How to use encrypted store-uri in Spring ImapIdleChannelAdapter - spring

Sample spring configuration is as below.
<int-mail:imap-idle-channel-adapter id="mailAdapter"
store-uri="imaps://${"username"}:${"password"}#imap-server:993/INBOX"
java-mail-properties="javaMailProperties"
channel="emails"
should-delete-messages="false"
should-mark-messages-as-read="true">
</int-mail:imap-idle-channel-adapter>
I wish to keep the password field encrypted in properties file and decrypt it in the code. I am not sure on how to set mailReceiver property of ImapIdleChannelAdapter to my custom version of ImapMailReceiver.
Please let me know if there is any way to do this.
All of my configurations are in XML as described above.
Above solution of adding the defifnation did not work may be I am doing something wrong. Then I tried using XML + Java configuration, as below.
#Configuration
public class EmailConfiguration {
#Bean
public ImapIdleChannelAdapter customAdapter() {
ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(mailReceiver());
adapter.setOutputChannel(outputChannel());
adapter.setErrorChannel(errorChannel());
adapter.setAutoStartup(true);
adapter.setShouldReconnectAutomatically(true);
adapter.setTaskScheduler(taskScheduler());
return adapter;
}
#Bean
public TaskImapMailReceiver mailReceiver() {
TaskImapMailReceiver mailReceiver = new TaskImapMailReceiver("imaps://[username]:[password]#imap.googlemail.com:993/inbox");
mailReceiver.setShouldDeleteMessages(false);
mailReceiver.setShouldMarkMessagesAsRead(true);
//mailReceiver.setJavaMailProperties(javaMailProperties());
mailReceiver.setMaxFetchSize(Integer.MAX_VALUE);
return mailReceiver;
}
}
Also created empty errorChannel,outputChannel etc. I observed that Spring creates two instances one with xml config and other with java #Configuration. Where it was expected to use only java configuration. If I remove the xml config tag
then it provides sigle imap instance with my mailReceiver but runs only once does not go periodic. also does not show IMAPS logs.
Just wondering if I need to do so much to encrypt the password. Is somthing wrong with my approach.

Use Java configuration instead of XML...
#Configuration
public class MyConfigClass {
#Bean
public MyMailReceiver receiver() {
...
}
#Bean
public ImapIdleChannelAdapter adapter() {
ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(receiver());
...
return adapter;
}
}
If you are using XML for everything else, simply add this class as a <bean/> to your XML.
EDIT
Here's an example that works fine for me...
#SpringBootApplication
public class So42298254Application {
public static void main(String[] args) {
SpringApplication.run(So42298254Application.class, args);
}
#Bean
public TestMailServer.ImapServer imapServer() {
return TestMailServer.imap(0);
}
#Bean
public ImapMailReceiver receiver() {
ImapMailReceiver imapMailReceiver = new ImapMailReceiver(imapUrl("user", "pw"));
imapMailReceiver.setHeaderMapper(new DefaultMailHeaderMapper()); // converts the MimeMessage to a String
imapMailReceiver.setUserFlag("testSIUserFlag"); // needed by the SI test server
Properties javaMailProperties = new Properties();
javaMailProperties.put("mail.debug", "true");
imapMailReceiver.setJavaMailProperties(javaMailProperties);
return imapMailReceiver;
}
private String imapUrl(String user, String pw) {
return "imap://"
+ user + ":" + pw
+ "#localhost:" + imapServer().getPort() + "/INBOX";
}
#Bean
public ImapIdleChannelAdapter adapter() {
ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(receiver());
adapter.setOutputChannelName("handleMail");
return adapter;
}
#ServiceActivator(inputChannel = "handleMail")
public void handle(String mail, #Header(MailHeaders.FROM) Object from) {
System.out.println(mail + " from:" + from);
imapServer().resetServer(); // so we'll get the email again
}
}

My intention was to use encrypted passwords in properties files.
So I changed my approach of getting into email receiving classes. I added inherited PropertyPlaceholderConfigurer and implemented method convertPropertyValue() as below.
public class EncryptationAwarePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static final Logger logger = LoggerFactory.getLogger(EncryptationAwarePropertyPlaceholderConfigurer.class);
#Override
protected String convertPropertyValue(String originalValue) {
if (originalValue.contains("{<ENC>}") && originalValue.contains("{</ENC>}")) {
String encryptedTaggedValue = originalValue.substring(originalValue.indexOf("{<ENC>}"), originalValue.indexOf("{</ENC>}") + 8);
String encryptedValue = originalValue.substring(originalValue.indexOf("{<ENC>}") + 7, originalValue.indexOf("{</ENC>}"));
try {
String decryptedValue = EncrypDecriptUtil.decrypt(encryptedValue);//EncrypDecriptUtil is my class for encription and decryption
originalValue = originalValue.replace(encryptedTaggedValue, decryptedValue);
} catch (GeneralSecurityException e) {
logger.error("failed to decrypt property returning original value as in properties file.", e);
}
}
return originalValue;
}
}
And changed properties file to enclose encrypted value in custuom ENC tag
as
mail.imap.task.url=imap://username:{<ENC>}encryptedPassword{</ENC>}#imap.googlemail.com:993/inbox

Related

How to run Quarkus programatically in Test mode

I am trying to run acceptance tests with concordion fixtures in a quarkus project. Concordion does not work with Junit5 so I am using its original #Run(ConcordionRunner.class).
I am creating a superclass to start my quarkus application before tests like that:
#RunWith(ConcordionRunner.class)
public abstract class AbstractFixture {
public static RunningQuarkusApplication application;
protected static RequestSpecification server;
protected AbstractFixture() {
setUp();
}
public void setUp() {
if(application == null) {
startApplication();
server = new RequestSpecBuilder()
.setPort(8081)
.setContentType(ContentType.JSON)
.build();
}
}
private void startApplication() {
try {
PathsCollection.Builder rootBuilder = PathsCollection.builder();
Path testClassLocation = PathTestHelper.getTestClassesLocation(getClass());
rootBuilder.add(testClassLocation);
final Path appClassLocation = PathTestHelper.getAppClassLocationForTestLocation(
testClassLocation.toString());
rootBuilder.add(appClassLocation);
application = QuarkusBootstrap.builder()
.setIsolateDeployment(false)
.setMode(QuarkusBootstrap.Mode.TEST)
.setProjectRoot(Paths.get("").normalize().toAbsolutePath())
.setApplicationRoot(rootBuilder.build())
.build()
.bootstrap()
.createAugmentor()
.createInitialRuntimeApplication()
.run();
} catch (BindException e) {
e.printStackTrace();
System.out.println("Address already in use - which is fine!");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
The code above is working but I can't change the default port 8081 to any other.
If I print the config property in my Test class like below, it prints the port correctly, but quarkus is not running on it:
public class HelloFixture extends AbstractFixture {
public String getGreeting() {
Response response = given(server).when().get("/hello");
System.out.println("Config[port]: " + application.getConfigValue("quarkus.http.port", String.class));
return response.asString();
}
}
How can I specify the configuration file or property programatically before run?
I found the answer. At first, I was referencing the wrong property "quarkus.http.port" instead of "quarkus.http.test-port".
Despite that, I found the way to override properties before run:
...
StartupAction action = QuarkusBootstrap.builder()
.setIsolateDeployment(false)
.setMode(QuarkusBootstrap.Mode.TEST)
.setProjectRoot(Paths.get("").normalize().toAbsolutePath())
.setApplicationRoot(rootBuilder.build())
.build()
.bootstrap()
.createAugmentor()
.createInitialRuntimeApplication();
action.overrideConfig(getConfigOverride());
application = action.run();
...
private Map<String, String> getConfigOverride() {
Map<String, String> config = new HashMap<>();
config.put("quarkus.http.test-port", "18082");
return config;
}

how to Spring integration SftpOutboundGateway setFilter Custom Argument?

How can I pass a parameter as a filter condition when getting the file list of the SFTP server from MessagingGateway?
My SftpMessageGateway code
#MessagingGateway
public interface SftpMessageGateway {
#Gateway(requestChannel = "getSftpChannel")
List<SftpFileInfo> getIconListByProductUiId(#Payloads("productUiId") String productUiId);
Integration Config
#Bean
public SessionFactory<ChannelSftp.LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost(host);
factory.setPort(port);
factory.setUser(id);
factory.setPassword(password);
factory.setAllowUnknownKeys(true);
return new CachingSessionFactory<>(factory);
}
#Bean
#ServiceActivator(inputChannel = "getSftpChannel")
public MessageHandler getMessageHandler() {
SftpOutboundGateway outboundGateway = new SftpOutboundGateway(sftpSessionFactory(), "ls", "'" + uploadPath + "'");
outboundGateway.setOption(AbstractRemoteFileOutboundGateway.Option.NAME_ONLY);
outboundGateway.setFilter(new SftpSimplePatternFileListFilter("*alpha*"));
outboundGateway.setFilter(new SftpSimplePatternFileListFilter("I want get custom argument)); <----
return outboundGateway;
}
You can set only one filter into a gateway, however there is a CompositeFileListFilter where you can combine a set of filters, include any custom impl of the FileListFilter.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/file.html#remote-persistent-flf
You can refer following code snippet for implementing FileListFilter. My use case was to fetch most latest file uploaded in SFTP directory.
#Component
public class LastModifiedFileFilter implements FileListFilter<LsEntry> {
#Override
public List<LsEntry> filterFiles(LsEntry[] files) {
List<LsEntry> result = new ArrayList<LsEntry>();
Vector<LsEntry> list = new Vector<LsEntry>();
Collections.addAll(list, files);
ChannelSftp.LsEntry lastModifiedEntry = Collections.max(list,
(Comparator.comparingInt(entry -> entry.getAttrs().getMTime())));
result.add(lastModifiedEntry);
return result;
}
}
Once you have your own custom filter in place then you need to 'Chain' it with your other filters in SftpOutboundGateway object. For your reference, I did it this way
ChainFileListFilter<LsEntry> filterList = new ChainFileListFilter<LsEntry>();
filterList.addFilter(new SftpSimplePatternFileListFilter("*alpha*"));
filterList.addFilter(new LastModifiedFileFilter());
setFilter(filterList);
For me, it will now fetch latest file having "alpha" string present in its name. Hope this helps.

Spring ws - Datahandler with Swaref still null

I used the Spring boot starter web services to develop a SOAP with attachment service.
For an unknown reason attachments aren't unmarshalled.. Jaxb Unmarshaller is used but the property AttachmentUnmarshaller inside is "null" ...so probably the reason why DataHandler unmarshalling isn't done ??
As in a JEE environment the attachmentUnmarshaller is handle by jaxws .. how configure it in a standalone process like spring boot on tomcat ??
Java version : 8_0_191
Spring boot version : 2.1
I faced similar issue, but with marshalling.
Jaxb2Marshaller has its own implementations of AttachmentMarshaller and AttachmentUnarshaller. But for these to work, mtomEnabled property should be set to true. If it's not, defaults will be used, which are not instantiated.
Try setting setMtomEnabled(true) on your Jaxb2Marshaller.
This will probably solve your issue.
For people, who encounter same issue with marshalling - it's a bit more complicated. Jaxb2 AttachmentMarshaller is not correctly implemented as per WS-I Attachment Profile 1.0 - http://www.ws-i.org/Profiles/AttachmentsProfile-1.0.html#Example_Attachment_Description_Using_swaRef
You will have to override marshalling behavior of Jaxb2Marshaller then.
Notice: this solution assumes that MTOM is always disabled.
#Configuration
class SOAPConfiguration {
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller() {
#Override
public void marshal(Object graph, Result result, #Nullable MimeContainer mimeContainer) throws XmlMappingException {
try {
javax.xml.bind.Marshaller marshaller = createMarshaller();
if (mimeContainer != null) {
marshaller.setAttachmentMarshaller(
new SwaRefAttachmentMarshaller(mimeContainer)
);
marshaller.marshal(graph, result);
} else {
super.marshal(graph, result, null);
}
} catch (JAXBException ex) {
throw convertJaxbException(ex);
}
}
};
marshaller.setPackagesToScan("my.package");
marshaller.setMtomEnabled(false);
return marshaller;
}
private class SwaRefAttachmentMarshaller extends AttachmentMarshaller {
private final MimeContainer mimeContainer;
private SwaRefAttachmentMarshaller(MimeContainer mimeContainer) {
this.mimeContainer = mimeContainer;
}
#Override
public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
return null;
}
#Override
public String addMtomAttachment(byte[] data, int offset, int length, String mimeType, String elementNamespace, String elementLocalName) {
return null;
}
#Override
public String addSwaRefAttachment(DataHandler data) {
String attachmentId = UUID.randomUUID().toString();
mimeContainer.addAttachment("<" + attachmentId + ">", data);
return "cid:" + attachmentId;
}
}
}

Spring Boot Gemfire Server Configuration

I am trying to understand how to host a Spring Boot Gemfire server process.
I found this example Spring Gemfire Server
The problem I am having is the the server I am trying to add to the cluster is not showing up in the cluster after I start the process.
Here are the steps I am taking:
Start a new locator locally (default port): gfsh>start locator --name=loc-one
I want to add this SpringBootGemfireServer to the cluster:
note I have commented out the embeded locator start-up - I want to add this to the existing locator already running
#SpringBootApplication
#SuppressWarnings("unused")
public class SpringGemFireServerApplication {
private static final boolean DEFAULT_AUTO_STARTUP = true;
public static void main(String[] args) {
SpringApplication.run(SpringGemFireServerApplication.class, args);
}
#Bean
static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertyPlaceholderConfigurer();
}
private String applicationName() {
return SpringGemFireServerApplication.class.getSimpleName();
}
#Bean
Properties gemfireProperties(
#Value("${gemfire.log.level:config}") String logLevel,
#Value("${gemfire.locator.host-port:localhost[10334]}") String locatorHostPort,
#Value("${gemfire.manager.port:1099}") String managerPort) {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("log-level", logLevel);
//gemfireProperties.setProperty("start-locator", locatorHostPort);
//gemfireProperties.setProperty("jmx-manager", "true");
//gemfireProperties.setProperty("jmx-manager-port", managerPort);
//gemfireProperties.setProperty("jmx-manager-start", "true");
return gemfireProperties;
}
#Bean
CacheFactoryBean gemfireCache(#Qualifier("gemfireProperties") Properties gemfireProperties) {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setProperties(gemfireProperties);
return gemfireCache;
}
#Bean
CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache,
#Value("${gemfire.cache.server.bind-address:localhost}") String bindAddress,
#Value("${gemfire.cache.server.hostname-for-clients:localhost}") String hostNameForClients,
#Value("${gemfire.cache.server.port:40404}") int port) {
CacheServerFactoryBean gemfireCacheServer = new CacheServerFactoryBean();
gemfireCacheServer.setCache(gemfireCache);
gemfireCacheServer.setAutoStartup(DEFAULT_AUTO_STARTUP);
gemfireCacheServer.setBindAddress(bindAddress);
gemfireCacheServer.setHostNameForClients(hostNameForClients);
gemfireCacheServer.setPort(port);
return gemfireCacheServer;
}
#Bean
PartitionedRegionFactoryBean<Long, Long> factorialsRegion(Cache gemfireCache,
#Qualifier("factorialsRegionAttributes") RegionAttributes<Long, Long> factorialsRegionAttributes) {
PartitionedRegionFactoryBean<Long, Long> factorialsRegion = new PartitionedRegionFactoryBean<>();
factorialsRegion.setAttributes(factorialsRegionAttributes);
factorialsRegion.setCache(gemfireCache);
factorialsRegion.setClose(false);
factorialsRegion.setName("Factorials");
factorialsRegion.setPersistent(false);
return factorialsRegion;
}
#Bean
#SuppressWarnings("unchecked")
RegionAttributesFactoryBean factorialsRegionAttributes() {
RegionAttributesFactoryBean factorialsRegionAttributes = new RegionAttributesFactoryBean();
factorialsRegionAttributes.setCacheLoader(factorialsCacheLoader());
factorialsRegionAttributes.setKeyConstraint(Long.class);
factorialsRegionAttributes.setValueConstraint(Long.class);
return factorialsRegionAttributes;
}
FactorialsCacheLoader factorialsCacheLoader() {
return new FactorialsCacheLoader();
}
class FactorialsCacheLoader implements CacheLoader<Long, Long> {
// stupid, naive implementation of Factorial!
#Override
public Long load(LoaderHelper<Long, Long> loaderHelper) throws CacheLoaderException {
long number = loaderHelper.getKey();
assert number >= 0 : String.format("Number [%d] must be greater than equal to 0", number);
if (number <= 2L) {
return (number < 2L ? 1L : 2L);
}
long result = number;
while (number-- > 1L) {
result *= number;
}
return result;
}
#Override
public void close() {
}
}
}
When I go to gfsh>connect list members
I only see the locator.
I haven't verified the full configuration to check whether there's something else wrong, but the main issue I see right now is that you seem to be confusing the start-locator property (automatically starts a locator in the current process when the member connects to the distributed system and stops the locator when the member disconnects) with the locators property (the list of locators used by system members, it must be configured consistently for every member of the distributed system). Since you're not correctly setting the locators property when configuring the server, it just can't join the existing distributed system because it doesn't know which locator to connect to.
The default locator port used by GemFire is 10334, so you should change your gemfireProperties method as follows:
#Bean
Properties gemfireProperties(#Value("${gemfire.log.level:config}") String logLevel, #Value("${gemfire.locator.host-port:localhost[10334]}") String locatorHostPort, #Value("${gemfire.manager.port:1099}") String managerPort) {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("log-level", logLevel);
// You can directly use the locatorHostPort variable instead.
gemfireProperties.setProperty("locators", "localhost[10334]");
return gemfireProperties;
}
Hope this helps.
Cheers.

#MessageMapping with placeholders

I am working with Spring-websocket and I have the following problem:
I am trying to put a placeholder inside a #MessageMapping annotation in order to get the url from properties. It works with #RequestMapping but not with #MessageMapping.
If I use this placeholder, the URL is null. Any idea or suggestion?
Example:
#RequestMapping(value= "${myProperty}")
#MessageMapping("${myProperty}")
Rossen Stoyanchev added placeholder support for #MessageMapping and #SubscribeMapping methods.
See Jira issue: https://jira.spring.io/browse/SPR-13271
Spring allows you to use property placeholders in #RequestMapping, but not in #MessageMapping. This is 'cause the MessageHandler. So, we need to override the default MessageHandler to do this.
WebSocketAnnotationMethodMessageHandler does not support placeholders and you need add this support yourself.
For simplicity I just created another WebSocketAnnotationMethodMessageHandler class in my project at the same package of the original, org.springframework.web.socket.messaging, and override getMappingForMethod method from SimpAnnotationMethodMessageHandler with same content, changing only how SimpMessageMappingInfo is contructed using this with this methods (private in WebSocketAnnotationMethodMessageHandler):
private SimpMessageMappingInfo createMessageMappingCondition(final MessageMapping annotation) {
return new SimpMessageMappingInfo(SimpMessageTypeMessageCondition.MESSAGE, new DestinationPatternsMessageCondition(
this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}
private SimpMessageMappingInfo createSubscribeCondition(final SubscribeMapping annotation) {
final SimpMessageTypeMessageCondition messageTypeMessageCondition = SimpMessageTypeMessageCondition.SUBSCRIBE;
return new SimpMessageMappingInfo(messageTypeMessageCondition, new DestinationPatternsMessageCondition(
this.resolveAnnotationValues(annotation.value()), this.getPathMatcher()));
}
These methods now will resolve value considering properties (calling resolveAnnotationValues method), so we need use something like this:
private String[] resolveAnnotationValues(final String[] destinationNames) {
final int length = destinationNames.length;
final String[] result = new String[length];
for (int i = 0; i < length; i++) {
result[i] = this.resolveAnnotationValue(destinationNames[i]);
}
return result;
}
private String resolveAnnotationValue(final String name) {
if (!(this.getApplicationContext() instanceof ConfigurableApplicationContext)) {
return name;
}
final ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) this.getApplicationContext();
final ConfigurableBeanFactory configurableBeanFactory = applicationContext.getBeanFactory();
final String placeholdersResolved = configurableBeanFactory.resolveEmbeddedValue(name);
final BeanExpressionResolver exprResolver = configurableBeanFactory.getBeanExpressionResolver();
if (exprResolver == null) {
return name;
}
final Object result = exprResolver.evaluate(placeholdersResolved, new BeanExpressionContext(configurableBeanFactory, null));
return result != null ? result.toString() : name;
}
You still need to define a PropertySourcesPlaceholderConfigurer bean in your configuration.
If you are using XML based configuration, include something like this:
<context:property-placeholder location="classpath:/META-INF/spring/url-mapping-config.properties" />
If you are using Java based configuration, you can try in this way:
#Configuration
#PropertySources(value = #PropertySource("classpath:/META-INF/spring/url-mapping-config.properties"))
public class URLMappingConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Obs.: in this case, url-mapping-config.properties file are in a gradle/maven project in src\main\resources\META-INF\spring folder and content look like this:
myPropertyWS=urlvaluews
This is my sample controller:
#Controller
public class WebSocketController {
#SendTo("/topic/test")
#MessageMapping("${myPropertyWS}")
public String test() throws Exception {
Thread.sleep(4000); // simulated delay
return "OK";
}
}
With default MessageHandler startup log will print something like this:
INFO: Mapped "{[/${myPropertyWS}],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception
And with our MessageHandler now print this:
INFO: Mapped "{[/urlvaluews],messageType=[MESSAGE]}" onto public java.lang.String com.brunocesar.controller.WebSocketController.test() throws java.lang.Exception
See in this gist the full WebSocketAnnotationMethodMessageHandler implementation.
EDIT: this solution resolves the problem for versions before 4.2 GA. For more information, see this jira.
Update :
Now I understood what you mean, but I think that is not possible(yet).
Documentation does not mention anything related to Path mapping URIs.
Old answer
Use
#MessageMapping("/handler/{myProperty}")
instead of
#MessageMapping("/handler/${myProperty}")
And use it like this:
#MessageMapping("/myHandler/{username}")
public void handleTextMessage(#DestinationVariable String username,Message message) {
//do something
}
#MessageMapping("/chat/{roomId}")
public Message handleMessages(#DestinationVariable("roomId") String roomId, #Payload Message message, Traveler traveler) throws Exception {
System.out.println("Message received for room: " + roomId);
System.out.println("User: " + traveler.toString());
// store message in database
message.setAuthor(traveler);
message.setChatRoomId(Integer.parseInt(roomId));
int id = MessageRepository.getInstance().save(message);
message.setId(id);
return message;
}

Resources