Some improvements for SMB module

* Improve permission information returned in `SmbFileInfo`
* Improve documentation for `SmbFileInfo.getPermissions()`
* Add Junit tests to verify all outbound gateway functionality
* Clean test directory of mistakenly migrated SMB extension code
This commit is contained in:
Gregory Bragg
2022-05-26 12:55:37 -04:00
committed by GitHub
parent 751d8084b4
commit 0cf1dfee7e
5 changed files with 208 additions and 208 deletions

View File

@@ -17,7 +17,6 @@
package org.springframework.integration.smb.session;
import java.io.IOException;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -91,23 +90,70 @@ public class SmbFileInfo extends AbstractFileInfo<SmbFile> {
}
/**
* An Access Control Entry (ACE) is an element in a security descriptor
* such as those associated with files and directories. The Windows OS
* determines which users have the necessary permissions to access objects
* based on these entries.
* @return a list of Access Control Entry (ACE) objects representing
* the security descriptor associated with this file or directory.
* An Access Control Entry (ACE) is an element in a security descriptor such as
* those associated with files and directories. The Windows OS determines which
* users have the necessary permissions to access objects based on these entries.
* A readable, formatted list of security descriptor entries and associated
* permissions will be returned by this implementation.
*
* <pre>
* WNET\alice - Deny Write, Deny Modify, Direct - This folder only
* SYSTEM - Allow Read, Allow Write, Allow Modify, Allow Execute, Allow Delete, Inherited - This folder only
* WNET\alice - Allow Read, Allow Write, Allow Modify, Allow Execute, Allow Delete, Inherited - This folder only
* Administrators - Allow Read, Allow Write, Allow Modify, Allow Execute, Allow Delete, Inherited - This folder only
* </pre>
*
* @return a list of Access Control Entry (ACE) objects representing the security
* descriptor entry and permissions associated with this file or directory.
* @see jcifs.ACE
* @see jcifs.SID
*/
@Override
public String getPermissions() {
ACE[] aceArray = null;
ACE[] aces = null;
try {
aceArray = this.smbFile.getSecurity(true);
aces = this.smbFile.getSecurity(true);
}
catch (IOException se) {
logger.error("Unable to determine security descriptor information for this SmbFile", se);
catch (IOException ioe) {
logger.error("Unable to determine security descriptor information for this SmbFile", ioe);
return null;
}
return (aceArray == null) ? null : Arrays.toString(aceArray);
StringBuilder sb = new StringBuilder();
for (ACE ace : aces) {
sb.append(ace.getSID().toDisplayString());
sb.append(" - ");
if ((ace.getAccessMask() & ACE.FILE_READ_DATA) != 0) {
sb.append(ace.isAllow() ? "Allow " : "Deny ");
sb.append("Read, ");
}
if ((ace.getAccessMask() & ACE.FILE_WRITE_DATA) != 0) {
sb.append(ace.isAllow() ? "Allow " : "Deny ");
sb.append("Write, ");
}
if ((ace.getAccessMask() & ACE.FILE_APPEND_DATA) != 0) {
sb.append(ace.isAllow() ? "Allow " : "Deny ");
sb.append("Modify, ");
}
if ((ace.getAccessMask() & ACE.FILE_EXECUTE) != 0) {
sb.append(ace.isAllow() ? "Allow " : "Deny ");
sb.append("Execute, ");
}
if ((ace.getAccessMask() & ACE.FILE_DELETE) != 0
|| (ace.getAccessMask() & ACE.DELETE) != 0) {
sb.append(ace.isAllow() ? "Allow " : "Deny ");
sb.append("Delete, ");
}
sb.append(ace.isInherited() ? "Inherited - " : "Direct - ");
sb.append(ace.getApplyToText());
sb.append("\n");
}
logger.debug(this.getFilename());
logger.debug("\n" + sb.toString());
return sb.toString();
}
@Override

View File

@@ -1,148 +0,0 @@
/*
* 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.
* 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.smb;
import java.util.Scanner;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.smb.outbound.SmbMessageHandler;
import org.springframework.integration.smb.session.SmbSessionFactory;
import org.springframework.messaging.support.GenericMessage;
import jcifs.DialectVersion;
/**
* Starts the Spring Context and will initialize the Spring Integration routes.
*
* @author Gunnar Hillert
* @author Artem Bilan
* @author Gregory Bragg
*
* @since 1.0
*
*/
public final class Main {
private static final Log LOGGER = LogFactory.getLog(Main.class);
private Main() { }
/**
* Load the Spring Integration Application Context
*
* @param args - command line arguments
*/
public static void main(final String... args) {
final Scanner scanner = new Scanner(System.in);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("\n================================================================="
+ "\n "
+ "\n Welcome to the Spring Integration SMB Test Client "
+ "\n "
+ "\n For more information please visit: "
+ "\n https://github.com/SpringSource/spring-integration-extensions "
+ "\n "
+ "\n=================================================================");
}
final GenericXmlApplicationContext context = new GenericXmlApplicationContext();
LOGGER.info("Please enter the: ");
LOGGER.info("\t- SMB Host");
LOGGER.info("\t- SMB Share and Directory");
LOGGER.info("\t- SMB Username");
LOGGER.info("\t- SMB Password");
LOGGER.info("Host: ");
final String host = scanner.nextLine();
LOGGER.info("Share and Directory (e.g. myFile/path/to/): ");
final String shareAndDir = scanner.nextLine();
LOGGER.info("Username (e.g. guest): ");
final String username = scanner.nextLine();
LOGGER.info("Password (can be empty): ");
final String password = scanner.nextLine();
context.getEnvironment().getSystemProperties().put("host", host);
context.getEnvironment().getSystemProperties().put("shareAndDir", shareAndDir);
context.getEnvironment().getSystemProperties().put("username", username);
context.getEnvironment().getSystemProperties().put("password", password);
context.load("classpath:META-INF/spring/integration/*-context.xml");
context.registerShutdownHook();
context.refresh();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("\n========================================================="
+ "\n "
+ "\n Please press 'q + Enter' to quit the application. "
+ "\n "
+ "\n=========================================================");
}
SmbSessionFactory smbSessionFactory = context.getBean("smbSession", SmbSessionFactory.class);
smbSessionFactory.setSmbMinVersion(DialectVersion.SMB210);
smbSessionFactory.setSmbMaxVersion(DialectVersion.SMB311);
LOGGER.info("Polling from Share: " + smbSessionFactory.getUrl());
// Create a test text file on the SMB file share
SmbMessageHandler handlerTxt = new SmbMessageHandler(smbSessionFactory);
handlerTxt.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
handlerTxt.setFileNameGenerator(message -> "handlerContent.txt");
handlerTxt.setAutoCreateDirectory(true);
handlerTxt.setUseTemporaryFileName(false);
handlerTxt.setBeanFactory(context.getBeanFactory());
handlerTxt.afterPropertiesSet();
handlerTxt.handleMessage(new GenericMessage<String>("hello, my text"));
// Create a test binary file on the SMB file share using a temporary filename
SmbMessageHandler handlerBin = new SmbMessageHandler(smbSessionFactory);
handlerBin.setRemoteDirectoryExpression(new LiteralExpression("remote-target-dir"));
handlerBin.setFileNameGenerator(message -> "handlerContent.bin");
handlerBin.setAutoCreateDirectory(true);
handlerBin.setUseTemporaryFileName(true);
handlerBin.setBeanFactory(context.getBeanFactory());
handlerBin.afterPropertiesSet();
handlerBin.handleMessage(new GenericMessage<byte[]>("hello, my bytes".getBytes()));
while (true) {
final String input = scanner.nextLine();
if ("q".equals(input.trim())) {
scanner.close();
context.close();
break;
}
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Exiting application...bye.");
}
System.exit(0);
}
}

View File

@@ -23,6 +23,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -66,6 +67,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import jcifs.smb.SmbException;
import jcifs.smb.SmbFile;
import jcifs.smb.SmbFileInputStream;
/**
* The actual SMB share must be configured in class 'SmbTestSupport'
@@ -83,6 +85,10 @@ import jcifs.smb.SmbFile;
* |-- subSmbSource/
* |-- subSmbSource1.txt - contains 'subSource1'
* |-- subSmbSource2.txt - contains 'subSource2'
* |-- subSmbSource2/ - directory will be created in testSmbPutFlow
* |-- subSmbSource2-1.txt - file will be created in testSmbPutFlow and deleted in testSmbRmFlow
* |-- subSmbSource2-2.txt - file will be created in testSmbMputFlow
* |-- subSmbSource2-3.txt - file will be created in testSmbMputFlow and renamed in testSmbMvFlow to subSmbSource-MV-Flow-Renamed.txt
* smbTarget/
* </pre>
*
@@ -215,7 +221,6 @@ public class SmbTests extends SmbTestSupport {
@Test
public void testSmbOutboundFlowWithSmbRemoteTemplate() {
SmbRemoteFileTemplate smbTemplate = new SmbRemoteFileTemplate(sessionFactory());
IntegrationFlow flow = f -> f
.handle(Smb.outboundAdapter(smbTemplate)
@@ -244,7 +249,6 @@ public class SmbTests extends SmbTestSupport {
@Test
public void testSmbOutboundFlowWithSmbRemoteTemplateAndMode() {
SmbRemoteFileTemplate smbTemplate = new SmbRemoteFileTemplate(sessionFactory());
IntegrationFlow flow = f -> f
.handle(Smb.outboundAdapter(smbTemplate, FileExistsMode.APPEND)
@@ -276,6 +280,36 @@ public class SmbTests extends SmbTestSupport {
registration.destroy();
}
@Test
public void testSmbGetFlow() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.GET, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.STREAM)
.fileExistsMode(FileExistsMode.IGNORE)
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')")
.charset(StandardCharsets.UTF_8.name())
.useTemporaryFileName(true))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String fileName = "smbSource/subSmbSource/subSmbSource2.txt";
registration.getInputChannel().send(new GenericMessage<>(fileName));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
SmbFileInputStream sfis = (SmbFileInputStream) result.getPayload();
assertThat(sfis).isNotNull();
try {
sfis.close();
}
catch (IOException ioe) {
}
registration.destroy();
}
@Test
@SuppressWarnings("unchecked")
public void testSmbMgetFlow() {
@@ -296,6 +330,7 @@ public class SmbTests extends SmbTestSupport {
registration.getInputChannel().send(new GenericMessage<>(dir + "*"));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<File> localFiles = (List<File>) result.getPayload();
assertThat(localFiles.size()).as("unexpected local files " + localFiles).isEqualTo(2);
@@ -329,6 +364,7 @@ public class SmbTests extends SmbTestSupport {
registration.getInputChannel().send(new GenericMessage<>(dir));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<SmbFileInfo> localFiles = (List<SmbFileInfo>) result.getPayload();
assertThat(localFiles.size()).as("unexpected local files " + localFiles).isEqualTo(2);
@@ -350,7 +386,7 @@ public class SmbTests extends SmbTestSupport {
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.NLST, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.ALL)
.options(AbstractRemoteFileOutboundGateway.Option.NOSORT)
.fileExistsMode(FileExistsMode.IGNORE)
.filterExpression("name matches 'subSmbSource|.*.txt'")
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
@@ -363,6 +399,7 @@ public class SmbTests extends SmbTestSupport {
registration.getInputChannel().send(new GenericMessage<>(dir));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<String> localFilenames = (List<String>) result.getPayload();
assertThat(localFilenames.size()).as("unexpected local filenames " + localFilenames).isEqualTo(2);
@@ -373,6 +410,115 @@ public class SmbTests extends SmbTestSupport {
registration.destroy();
}
@Test
public void testSmbPutFlow() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.PUT, "payload")
.useTemporaryFileName(false)
.fileNameExpression("headers['" + FileHeaders.FILENAME + "']")
.remoteDirectoryExpression("'smbSource/subSmbSource2/'")
.autoCreateDirectory(true))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String fileName = "subSmbSource2-1.txt";
Message<ByteArrayInputStream> message = MessageBuilder
.withPayload(new ByteArrayInputStream("subSmbSource2-1".getBytes(StandardCharsets.UTF_8)))
.setHeader(FileHeaders.FILENAME, fileName)
.build();
registration.getInputChannel().send(message);
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
String path = (String) result.getPayload();
assertThat(path).isNotNull();
assertThat(path.contains("subSmbSource2"));
registration.destroy();
}
@Test
public void testSmbRmFlow() {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.RM, "payload"))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String fileName = "smbSource/subSmbSource2/subSmbSource2-1.txt";
registration.getInputChannel().send(new GenericMessage<>(fileName));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
Boolean success = (Boolean) result.getPayload();
assertThat(success).isTrue();
registration.destroy();
}
@Test
@SuppressWarnings("unchecked")
public void testSmbMputFlow() throws IOException {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MPUT, "payload")
.options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
.useTemporaryFileName(false)
.remoteDirectoryExpression("'smbSource/subSmbSource2/'")
.autoCreateDirectory(true)
.localDirectoryExpression("'" + getTargetLocalDirectoryName() + "' + #remoteDirectory")
.localFilenameExpression("#remoteFileName.replaceFirst('smbSource', 'localTarget')"))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
File file1 = new File(getTargetLocalDirectoryName(), "subSmbSource2-2.txt");
File file2 = new File(getTargetLocalDirectoryName(), "subSmbSource2-3.txt");
file1.createNewFile();
file2.createNewFile();
List<File> files = new ArrayList<>();
files.add(file1);
files.add(file2);
Message<List<File>> message = MessageBuilder
.withPayload(files)
.build();
registration.getInputChannel().send(message);
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
List<String> remoteFilenames = (List<String>) result.getPayload();
assertThat(remoteFilenames).isNotNull();
assertThat(remoteFilenames.size()).as("unexpected remote filenames " + remoteFilenames).isEqualTo(2);
for (String filename : remoteFilenames) {
assertThat(filename.contains("subSmbSource2"));
}
registration.destroy();
}
@Test
public void testSmbMvFlow() throws IOException {
QueueChannel out = new QueueChannel();
IntegrationFlow flow = f -> f
.handle(
Smb.outboundGateway(sessionFactory(), AbstractRemoteFileOutboundGateway.Command.MV, "payload")
.renameExpression("'smbSource/subSmbSource2/subSmbSource-MV-Flow-Renamed.txt'"))
.channel(out);
IntegrationFlowRegistration registration = this.flowContext.registration(flow).register();
String fileName = "smbSource/subSmbSource2/subSmbSource2-3.txt";
registration.getInputChannel().send(new GenericMessage<>(fileName));
Message<?> result = out.receive(10_000);
assertThat(result).isNotNull();
Boolean success = (Boolean) result.getPayload();
assertThat(success).isTrue();
registration.destroy();
}
@Configuration
@EnableIntegration
@EnableIntegrationManagement

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-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.
@@ -39,7 +39,6 @@ import jcifs.smb.SmbFile;
/**
* @author Markus Spann
* @author Gunnar Hillert
* @since 1.0
*/
public class SmbInboundRemoteFileSystemSynchronizerTests extends AbstractBaseTests {

View File

@@ -1,43 +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-smb="http://www.springframework.org/schema/integration/smb"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/file https://www.springframework.org/schema/integration/file/spring-integration-file.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/smb https://www.springframework.org/schema/integration/smb/spring-integration-smb.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder />
<!--
smb://[[[domain;]username[:password]@]server[:port]/[[share/[dir/]file]]][?[param=value[param2=value2[...]]]
-->
<bean id="smbSession" class="org.springframework.integration.smb.session.SmbSessionFactory">
<property name="host" value="${host}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="shareAndDir" value="${shareAndDir}"/>
</bean>
<int-smb:inbound-channel-adapter local-directory="target/smb-transfer-work"
session-factory="smbSession" remote-directory="."
auto-create-local-directory="true" delete-remote-files="false"
channel="inboundChannel">
<int:poller fixed-rate="10000" max-messages-per-poll="1"/>
</int-smb:inbound-channel-adapter>
<int:channel id="inboundChannel">
<int:interceptors>
<int:wire-tap channel="loggit"/>
</int:interceptors>
</int:channel>
<int:logging-channel-adapter id="loggit" level="INFO"
logger-name="org.springframework.integration.samples.smb"
expression="'File Name: ' + payload.name + '(' + payload.length() + ')'"/>
<int-file:outbound-channel-adapter channel="inboundChannel" directory="target/smb-out"/>
</beans>