diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 68ef86f23..0a4694661 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -44,6 +44,7 @@ |cloud.aws.sns.enabled | `true` | Enables SNS integration. |cloud.aws.sns.endpoint | | |cloud.aws.sns.region | | +|cloud.aws.sns.verification | `true` | Defines if SNS massages will be verified. By default, verification is used. |cloud.aws.sqs.enabled | `true` | Enables SQS integration. |cloud.aws.sqs.endpoint | | |cloud.aws.sqs.handler.default-deletion-policy | | Configures global deletion policy used if deletion policy is not explicitly set on {@link SqsListener}. diff --git a/docs/src/main/asciidoc/sns.adoc b/docs/src/main/asciidoc/sns.adoc index 9f9c6c4d6..db763a2cb 100644 --- a/docs/src/main/asciidoc/sns.adoc +++ b/docs/src/main/asciidoc/sns.adoc @@ -104,6 +104,14 @@ SNS sends three type of requests to an HTTP topic listener endpoint, for each of * Notification request -> `@NotificationMessageMapping` * Unsubscription request -> `@NotificationUnsubscribeMapping` +[NOTE] +==== +Since 2.4.0 verification has been introduced for Notification request and is turned on by default. Verification is using same region as SNSClient is. +To turn off verification simply set property 'cloud.aws.sns.verification=false'. +With Localstack verification won't work, so you have to set property to 'false' if you want `@NotificationMessageMapping` to work properly. +For more information about SNS verification https://docs.awspring.io/spring-cloud-aws/docs/2.3.3/reference/html/index.html [here]. +==== + HTTP endpoints are based on Spring MVC controllers. Spring Cloud AWS added some custom argument resolvers to extract the message and subject out of the notification requests. diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfiguration.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfiguration.java index 56991279f..0a26d6f33 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfiguration.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfiguration.java @@ -21,17 +21,22 @@ import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.regions.Regions; import com.amazonaws.services.sns.AmazonSNS; import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.message.SnsMessageManager; import io.awspring.cloud.context.annotation.ConditionalOnMissingAmazonClient; import io.awspring.cloud.core.config.AmazonWebserviceClientFactoryBean; import io.awspring.cloud.core.region.RegionProvider; import io.awspring.cloud.core.region.StaticRegionProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -49,6 +54,8 @@ * @author Alain Sahli * @author EddĂș MelĂ©ndez * @author Maciej Walkowiak + * @author Manuel Wessner + * @author Matej Nedic */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(AmazonSNS.class) @@ -56,6 +63,8 @@ @ConditionalOnProperty(name = "cloud.aws.sns.enabled", havingValue = "true", matchIfMissing = true) public class SnsAutoConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(SnsAutoConfiguration.class); + private final AWSCredentialsProvider awsCredentialsProvider; private final RegionProvider regionProvider; @@ -81,16 +90,32 @@ public AmazonWebserviceClientFactoryBean amazonSNS(SnsPropertie return clientFactoryBean; } + @ConditionalOnProperty(name = "cloud.aws.sns.verification", havingValue = "true", matchIfMissing = true) + @ConditionalOnMissingBean(SnsMessageManager.class) + @Bean + public SnsMessageManager snsMessageManager(SnsProperties snsProperties) { + if (regionProvider == null) { + String defaultRegion = Regions.DEFAULT_REGION.getName(); + LOGGER.warn( + "RegionProvider bean not configured. Configuring SnsMessageManager with region " + defaultRegion); + return new SnsMessageManager(defaultRegion); + } + else { + return new SnsMessageManager(regionProvider.getRegion().getName()); + } + } + @Configuration(proxyBeanMethods = false) @ConditionalOnClass(WebMvcConfigurer.class) static class SnsWebConfiguration { @Bean - public WebMvcConfigurer snsWebMvcConfigurer(AmazonSNS amazonSns) { + public WebMvcConfigurer snsWebMvcConfigurer(AmazonSNS amazonSns, + Optional snsMessageManager) { return new WebMvcConfigurer() { @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(getNotificationHandlerMethodArgumentResolver(amazonSns)); + resolvers.add(getNotificationHandlerMethodArgumentResolver(amazonSns, snsMessageManager)); } }; } diff --git a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsProperties.java b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsProperties.java index f01a28064..8aa7d6fa8 100644 --- a/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsProperties.java +++ b/spring-cloud-aws-autoconfigure/src/main/java/io/awspring/cloud/autoconfigure/messaging/SnsProperties.java @@ -28,4 +28,17 @@ @ConfigurationProperties(prefix = "cloud.aws.sns") public class SnsProperties extends AwsClientProperties { + /** + * Defines if SNS massages will be verified. By default, verification is used. + */ + private boolean verification = true; + + public boolean getVerification() { + return verification; + } + + public void setVerification(boolean verification) { + this.verification = verification; + } + } diff --git a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfigurationTest.java b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfigurationTest.java index b8169da8a..8e7e6998f 100644 --- a/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfigurationTest.java +++ b/spring-cloud-aws-autoconfigure/src/test/java/io/awspring/cloud/autoconfigure/messaging/SnsAutoConfigurationTest.java @@ -25,6 +25,7 @@ import com.amazonaws.regions.Regions; import com.amazonaws.services.sns.AmazonSNS; import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.message.SnsMessageManager; import io.awspring.cloud.core.region.RegionProvider; import io.awspring.cloud.core.region.StaticRegionProvider; import io.awspring.cloud.messaging.endpoint.NotificationStatusHandlerMethodArgumentResolver; @@ -77,6 +78,7 @@ void enableSns_withMinimalConfig_shouldConfigureACompositeArgumentResolver() thr assertThat(compositeArgumentResolver.getResolvers()).hasSize(3); assertThat(getNotificationStatusHandlerMethodArgumentResolver(compositeArgumentResolver.getResolvers())) .hasFieldOrProperty("amazonSns").isNotNull(); + assertThat(context).hasSingleBean(SnsMessageManager.class); }); } @@ -89,6 +91,7 @@ void enableSns_withProvidedCredentials_shouldBeUsedToCreateClient() throws Excep // Assert assertThat(amazonSns).hasFieldOrPropertyWithValue("awsCredentialsProvider", SnsConfigurationWithCredentials.AWS_CREDENTIALS_PROVIDER); + assertThat(context).hasSingleBean(SnsMessageManager.class); }); } @@ -97,6 +100,16 @@ void disableSns() { this.contextRunner.withPropertyValues("cloud.aws.sns.enabled:false").run(context -> { assertThat(context).doesNotHaveBean(AmazonSNS.class); assertThat(context).doesNotHaveBean(AmazonSNSClient.class); + assertThat(context).doesNotHaveBean(SnsMessageManager.class); + }); + } + + @Test + void disableSnsVerification() { + this.contextRunner.withPropertyValues("cloud.aws.sns.verification:false").run(context -> { + assertThat(context).doesNotHaveBean(SnsMessageManager.class); + assertThat(context).hasSingleBean(AmazonSNS.class); + assertThat(context).hasSingleBean(AmazonSNSClient.class); }); } @@ -116,6 +129,7 @@ void enableSns_withCustomAmazonSnsClient_shouldBeUsedByTheArgumentResolver() thr handlerMethodArgumentResolver.getResolvers()); assertThat(notificationStatusHandlerMethodArgumentResolver).hasFieldOrPropertyWithValue("amazonSns", SnsConfigurationWithCustomAmazonClient.AMAZON_SNS); + assertThat(context).hasSingleBean(SnsMessageManager.class); }); } @@ -128,6 +142,7 @@ void enableSns_withRegionProvided_shouldBeUsedToCreateClient() throws Exception // Assert assertThat(ReflectionTestUtils.getField(amazonSns, "endpoint").toString()) .isEqualTo("https://" + Region.getRegion(Regions.EU_WEST_1).getServiceEndpoint("sns")); + assertThat(context).hasSingleBean(SnsMessageManager.class); }); } @@ -137,6 +152,7 @@ void enableSnsWithSpecificRegion() { AmazonSNSClient client = context.getBean(AmazonSNSClient.class); Object region = ReflectionTestUtils.getField(client, "signingRegion"); assertThat(region).isEqualTo(Regions.US_EAST_1.getName()); + assertThat(context).hasSingleBean(SnsMessageManager.class); }); } @@ -149,6 +165,7 @@ void enableSnsWithCustomEndpoint() { assertThat(endpoint).isEqualTo(URI.create("http://localhost:8090")); Boolean isEndpointOverridden = (Boolean) ReflectionTestUtils.getField(client, "isEndpointOverridden"); + assertThat(context).hasSingleBean(SnsMessageManager.class); assertThat(isEndpointOverridden).isTrue(); }); } @@ -162,6 +179,7 @@ void configuration_withGlobalClientConfiguration_shouldUseItForClient() { // Assert ClientConfiguration clientConfiguration = (ClientConfiguration) ReflectionTestUtils.getField(amazonSns, "clientConfiguration"); + assertThat(context).hasSingleBean(SnsMessageManager.class); assertThat(clientConfiguration.getProxyHost()).isEqualTo("global"); }); } @@ -175,6 +193,7 @@ void configuration_withSnsClientConfiguration_shouldUseItForClient() { // Assert ClientConfiguration clientConfiguration = (ClientConfiguration) ReflectionTestUtils.getField(amazonSns, "clientConfiguration"); + assertThat(context).hasSingleBean(SnsMessageManager.class); assertThat(clientConfiguration.getProxyHost()).isEqualTo("sns"); }); } @@ -189,6 +208,7 @@ void configuration_withGlobalAndSnsClientConfigurations_shouldUseSnsConfiguratio // Assert ClientConfiguration clientConfiguration = (ClientConfiguration) ReflectionTestUtils .getField(amazonSns, "clientConfiguration"); + assertThat(context).hasSingleBean(SnsMessageManager.class); assertThat(clientConfiguration.getProxyHost()).isEqualTo("sns"); }); } @@ -198,6 +218,7 @@ void doesNotFailWithoutWebMvcConfigurerOnClasspath() { this.contextRunner.withUserConfiguration(NoSpringMvcSnsConfiguration.class) .withClassLoader(new FilteredClassLoader(WebMvcConfigurer.class)).run((context) -> { assertThat(context).hasSingleBean(AmazonSNS.class); + assertThat(context).hasSingleBean(SnsMessageManager.class); }); } diff --git a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/config/annotation/SnsWebConfiguration.java b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/config/annotation/SnsWebConfiguration.java index ae1ec8175..3c9ebc359 100644 --- a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/config/annotation/SnsWebConfiguration.java +++ b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/config/annotation/SnsWebConfiguration.java @@ -17,8 +17,10 @@ package io.awspring.cloud.messaging.config.annotation; import java.util.List; +import java.util.Optional; import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.message.SnsMessageManager; import io.awspring.cloud.context.annotation.ConditionalOnClass; import org.springframework.context.annotation.Bean; @@ -37,11 +39,11 @@ public class SnsWebConfiguration { @Bean - public WebMvcConfigurer snsWebMvcConfigurer(AmazonSNS amazonSns) { + public WebMvcConfigurer snsWebMvcConfigurer(AmazonSNS amazonSns, Optional snsMessageManager) { return new WebMvcConfigurer() { @Override public void addArgumentResolvers(List argumentResolvers) { - argumentResolvers.add(getNotificationHandlerMethodArgumentResolver(amazonSns)); + argumentResolvers.add(getNotificationHandlerMethodArgumentResolver(amazonSns, snsMessageManager)); } }; } diff --git a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolver.java b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolver.java index 7bdbd0a1d..977c5560b 100644 --- a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolver.java +++ b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolver.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.List; +import com.amazonaws.services.sns.message.SnsMessageManager; import com.fasterxml.jackson.databind.JsonNode; import io.awspring.cloud.messaging.config.annotation.NotificationMessage; @@ -35,21 +36,29 @@ import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.messaging.converter.MessageConversionException; import org.springframework.util.StringUtils; /** * @author Agim Emruli + * @author Manuel Wessner + * @author Matej Nedic */ public class NotificationMessageHandlerMethodArgumentResolver extends AbstractNotificationMessageHandlerMethodArgumentResolver { private final List> messageConverter; - public NotificationMessageHandlerMethodArgumentResolver() { - this(Arrays.asList(new MappingJackson2HttpMessageConverter(), new StringHttpMessageConverter())); + private final SnsMessageManager snsMessageManager; + + public NotificationMessageHandlerMethodArgumentResolver(SnsMessageManager snsMessageManager) { + this(Arrays.asList(new MappingJackson2HttpMessageConverter(), new StringHttpMessageConverter()), + snsMessageManager); } - public NotificationMessageHandlerMethodArgumentResolver(List> messageConverter) { + public NotificationMessageHandlerMethodArgumentResolver(List> messageConverter, + SnsMessageManager snsMessageManager) { + this.snsMessageManager = snsMessageManager; this.messageConverter = messageConverter; } @@ -81,7 +90,9 @@ protected Object doResolveArgumentFromNotificationMessage(JsonNode content, Http MediaType mediaType = getMediaType(content); String messageContent = content.findPath("Message").asText(); - + if (snsMessageManager != null && content.has("SignatureVersion")) { + verifySignature(content.toString()); + } for (HttpMessageConverter converter : this.messageConverter) { if (converter.canRead(parameterType, mediaType)) { try { @@ -99,6 +110,16 @@ protected Object doResolveArgumentFromNotificationMessage(JsonNode content, Http "Error converting notification message with payload:" + messageContent); } + private void verifySignature(String payload) { + try (InputStream messageStream = new ByteArrayInputStream(payload.getBytes())) { + // Unmarshalling the message is not needed, but also done here + snsMessageManager.parseMessage(messageStream); + } + catch (IOException e) { + throw new MessageConversionException("Issue while verifying signature of Payload: '" + payload + "'", e); + } + } + private static final class ByteArrayHttpInputMessage implements HttpInputMessage { private final String content; diff --git a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverConfigurationUtils.java b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverConfigurationUtils.java index a1993a99e..14d809dcc 100644 --- a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverConfigurationUtils.java +++ b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverConfigurationUtils.java @@ -16,7 +16,10 @@ package io.awspring.cloud.messaging.endpoint.config; +import java.util.Optional; + import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.message.SnsMessageManager; import io.awspring.cloud.messaging.endpoint.NotificationMessageHandlerMethodArgumentResolver; import io.awspring.cloud.messaging.endpoint.NotificationStatusHandlerMethodArgumentResolver; import io.awspring.cloud.messaging.endpoint.NotificationSubjectHandlerMethodArgumentResolver; @@ -34,10 +37,16 @@ private NotificationHandlerMethodArgumentResolverConfigurationUtils() { throw new IllegalStateException("Can't instantiate a utility class"); } - public static HandlerMethodArgumentResolver getNotificationHandlerMethodArgumentResolver(AmazonSNS amazonSns) { + public static HandlerMethodArgumentResolver getNotificationHandlerMethodArgumentResolver(AmazonSNS amazonSns, + Optional snsMessageManager) { HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite(); composite.addResolver(new NotificationStatusHandlerMethodArgumentResolver(amazonSns)); - composite.addResolver(new NotificationMessageHandlerMethodArgumentResolver()); + if (snsMessageManager.isPresent()) { + composite.addResolver(new NotificationMessageHandlerMethodArgumentResolver(snsMessageManager.get())); + } + else { + composite.addResolver(new NotificationMessageHandlerMethodArgumentResolver(null)); + } composite.addResolver(new NotificationSubjectHandlerMethodArgumentResolver()); return composite; } diff --git a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBean.java b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBean.java index 47875dc55..74c5f83c2 100644 --- a/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBean.java +++ b/spring-cloud-aws-messaging/src/main/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBean.java @@ -16,7 +16,10 @@ package io.awspring.cloud.messaging.endpoint.config; +import java.util.Optional; + import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.message.SnsMessageManager; import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.util.Assert; @@ -33,9 +36,19 @@ public class NotificationHandlerMethodArgumentResolverFactoryBean private final AmazonSNS amazonSns; + private final SnsMessageManager snsMessageManager; + public NotificationHandlerMethodArgumentResolverFactoryBean(AmazonSNS amazonSns) { Assert.notNull(amazonSns, "AmazonSns must not be null"); + this.amazonSns = null; + snsMessageManager = null; + } + + public NotificationHandlerMethodArgumentResolverFactoryBean(AmazonSNS amazonSns, + SnsMessageManager snsMessageManager) { + Assert.notNull(amazonSns, "AmazonSns must not be null"); this.amazonSns = amazonSns; + this.snsMessageManager = snsMessageManager; } @Override @@ -45,7 +58,7 @@ public Class getObjectType() { @Override protected HandlerMethodArgumentResolver createInstance() throws Exception { - return getNotificationHandlerMethodArgumentResolver(this.amazonSns); + return getNotificationHandlerMethodArgumentResolver(this.amazonSns, Optional.ofNullable(snsMessageManager)); } } diff --git a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/ComplexNotificationEndpointControllerTest.java b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/ComplexNotificationEndpointControllerTest.java index 650d40e83..99a829d9c 100644 --- a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/ComplexNotificationEndpointControllerTest.java +++ b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/ComplexNotificationEndpointControllerTest.java @@ -17,6 +17,7 @@ package io.awspring.cloud.messaging.endpoint; import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.message.SnsMessageManager; import io.awspring.cloud.messaging.config.annotation.EnableSns; import io.awspring.cloud.messaging.endpoint.ComplexNotificationEndpointControllerTest.Config; import org.junit.jupiter.api.BeforeEach; @@ -148,6 +149,11 @@ void notification_unsubscribeConfirmationReceivedAsMessage_reSubscriptionCalledB @Import(ComplexNotificationTestController.class) static class Config { + @Bean + SnsMessageManager snsMessageManager() { + return Mockito.mock(SnsMessageManager.class); + } + @Bean AmazonSNS amazonSNS() { return Mockito.mock(AmazonSNS.class); diff --git a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationEndpointControllerTest.java b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationEndpointControllerTest.java index 5b190015d..ecba544ed 100644 --- a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationEndpointControllerTest.java +++ b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationEndpointControllerTest.java @@ -17,6 +17,7 @@ package io.awspring.cloud.messaging.endpoint; import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.message.SnsMessageManager; import io.awspring.cloud.messaging.config.annotation.EnableSns; import io.awspring.cloud.messaging.endpoint.NotificationEndpointControllerTest.Config; import org.junit.jupiter.api.BeforeEach; @@ -125,6 +126,11 @@ void notification_unsubscribeConfirmationReceivedAsMessage_reSubscriptionCalledB @Import(NotificationTestController.class) static class Config { + @Bean + SnsMessageManager snsMessageManager() { + return Mockito.mock(SnsMessageManager.class); + } + @Bean AmazonSNS amazonSNS() { return Mockito.mock(AmazonSNS.class); diff --git a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolverTest.java b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolverTest.java index 1598bcf37..778d3f91c 100644 --- a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolverTest.java +++ b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/NotificationMessageHandlerMethodArgumentResolverTest.java @@ -16,6 +16,8 @@ package io.awspring.cloud.messaging.endpoint; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.sns.message.SnsMessageManager; import org.junit.jupiter.api.Test; import org.springframework.core.MethodParameter; @@ -27,13 +29,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; class NotificationMessageHandlerMethodArgumentResolverTest { @Test void resolveArgument_wrongMessageType_reportsErrors() throws Exception { // Arrange - NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver(); + SnsMessageManager snsMessageManager = mock(SnsMessageManager.class); + NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver( + snsMessageManager); byte[] subscriptionRequestJsonContent = FileCopyUtils .copyToByteArray(new ClassPathResource("subscriptionConfirmation.json", getClass()).getInputStream()); @@ -54,7 +59,9 @@ void resolveArgument_wrongMessageType_reportsErrors() throws Exception { @Test void resolveArgument_notificationMessageTypeWithSubject_reportsErrors() throws Exception { // Arrange - NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver(); + SnsMessageManager snsMessageManager = mock(SnsMessageManager.class); + NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver( + snsMessageManager); byte[] subscriptionRequestJsonContent = FileCopyUtils .copyToByteArray(new ClassPathResource("notificationMessage.json", getClass()).getInputStream()); @@ -74,7 +81,9 @@ void resolveArgument_notificationMessageTypeWithSubject_reportsErrors() throws E @Test void supportsParameter_withIntegerParameterType_shouldReturnFalse() throws Exception { // Arrange - NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver(); + SnsMessageManager snsMessageManager = mock(SnsMessageManager.class); + NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver( + snsMessageManager); MethodParameter methodParameter = new MethodParameter( ReflectionUtils.findMethod(NotificationMethods.class, "methodWithIntegerParameterType", Integer.class), 0); @@ -86,4 +95,25 @@ void supportsParameter_withIntegerParameterType_shouldReturnFalse() throws Excep assertThat(supportsParameter).isTrue(); } + @Test + void resolveArgument_notificationMessageTypeWithSubject_reportsErrors_failsVerification() throws Exception { + // Arrange + SnsMessageManager snsMessageManager = new SnsMessageManager("eu-east-1"); + NotificationMessageHandlerMethodArgumentResolver resolver = new NotificationMessageHandlerMethodArgumentResolver( + snsMessageManager); + + byte[] subscriptionRequestJsonContent = FileCopyUtils + .copyToByteArray(new ClassPathResource("notificationMessage.json", getClass()).getInputStream()); + MockHttpServletRequest servletRequest = new MockHttpServletRequest(); + servletRequest.setContent(subscriptionRequestJsonContent); + + MethodParameter methodParameter = new MethodParameter( + ReflectionUtils.findMethod(NotificationMethods.class, "handleMethod", String.class, String.class), 0); + + // Assert + assertThatThrownBy(() -> resolver.resolveArgument(methodParameter, null, new ServletWebRequest(servletRequest), + null)).isInstanceOf(SdkClientException.class).hasMessageContaining( + "SigningCertUrl does not match expected endpoint. Expected sns.eu-east-1.amazonaws.com but received endpoint was sns.eu-west-1.amazonaws.com."); + } + } diff --git a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBeanTest.java b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBeanTest.java index e4ab54ea1..09ccc0916 100644 --- a/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBeanTest.java +++ b/spring-cloud-aws-messaging/src/test/java/io/awspring/cloud/messaging/endpoint/config/NotificationHandlerMethodArgumentResolverFactoryBeanTest.java @@ -17,6 +17,7 @@ package io.awspring.cloud.messaging.endpoint.config; import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.message.SnsMessageManager; import org.junit.jupiter.api.Test; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -32,8 +33,9 @@ class NotificationHandlerMethodArgumentResolverFactoryBeanTest { void getObjectType_defaultConfiguration_returnsHandlerMethodArgumentResolverType() throws Exception { // Arrange AmazonSNS amazonSns = mock(AmazonSNS.class); + SnsMessageManager snsMessageManager = mock(SnsMessageManager.class); NotificationHandlerMethodArgumentResolverFactoryBean factoryBean; - factoryBean = new NotificationHandlerMethodArgumentResolverFactoryBean(amazonSns); + factoryBean = new NotificationHandlerMethodArgumentResolverFactoryBean(amazonSns, snsMessageManager); // Act Class type = factoryBean.getObjectType(); @@ -46,8 +48,9 @@ void getObjectType_defaultConfiguration_returnsHandlerMethodArgumentResolverType void getObject_withDefaultConfiguration_createCompositeResolverWithAllDelegatedResolvers() throws Exception { // Arrange AmazonSNS amazonSns = mock(AmazonSNS.class); + SnsMessageManager snsMessageManager = mock(SnsMessageManager.class); NotificationHandlerMethodArgumentResolverFactoryBean factoryBean; - factoryBean = new NotificationHandlerMethodArgumentResolverFactoryBean(amazonSns); + factoryBean = new NotificationHandlerMethodArgumentResolverFactoryBean(amazonSns, snsMessageManager); factoryBean.afterPropertiesSet(); // Act @@ -61,7 +64,7 @@ void getObject_withDefaultConfiguration_createCompositeResolverWithAllDelegatedR @Test void createInstance_withNullSnsClient_reportsError() throws Exception { // Assert - assertThatThrownBy(() -> new NotificationHandlerMethodArgumentResolverFactoryBean(null)) + assertThatThrownBy(() -> new NotificationHandlerMethodArgumentResolverFactoryBean(null, null)) .isInstanceOf(IllegalArgumentException.class).hasMessageContaining("not be null"); } diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/bin/infrastructure.ts b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/bin/infrastructure.ts index 717174a24..ba92b39d1 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/bin/infrastructure.ts +++ b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/bin/infrastructure.ts @@ -4,4 +4,4 @@ import * as cdk from '@aws-cdk/core'; import { InfrastructureStack } from '../lib/infrastructure-stack'; const app = new cdk.App(); -new InfrastructureStack(app, 'InfrastructureStack'); +new InfrastructureStack(app, 'SnsSampleAppStack'); diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/lib/infrastructure-stack.ts b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/lib/infrastructure-stack.ts index 789e736a7..92579331e 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/lib/infrastructure-stack.ts +++ b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/lib/infrastructure-stack.ts @@ -16,6 +16,6 @@ export class InfrastructureStack extends cdk.Stack { topic.addSubscription(new subs.SqsSubscription(queue)); //URL from NGROK goes here - topic.addSubscription(new subs.UrlSubscription('http://1745bc39a0ae.ngrok.io/testTopic')); + topic.addSubscription(new subs.UrlSubscription('https://5d50-2a02-8109-8380-c8c-d911-1af7-8ab6-35e1.ngrok.io/testTopic')); } } diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/package.json b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/package.json index a267088bf..7c6dd2d8d 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/package.json +++ b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/infrastracture/package.json @@ -11,7 +11,7 @@ "cdk": "cdk" }, "devDependencies": { - "@aws-cdk/assert": "1.86.0", + "@aws-cdk/assert": "1.137.0", "@types/jest": "^26.0.10", "@types/node": "10.17.27", "aws-cdk": "^1.90.0", @@ -21,10 +21,10 @@ "typescript": "~3.9.7" }, "dependencies": { - "@aws-cdk/aws-sns": "^1.86.0", - "@aws-cdk/aws-sns-subscriptions": "^1.86.0", - "@aws-cdk/aws-sqs": "^1.86.0", - "@aws-cdk/core": "1.86.0", + "@aws-cdk/aws-sns": "^1.137.0", + "@aws-cdk/aws-sns-subscriptions": "^1.137.0", + "@aws-cdk/aws-sqs": "^1.137.0", + "@aws-cdk/core": "1.137.0", "source-map-support": "^0.5.16" } } diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/NotificationMappingController.java b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/NotificationMappingController.java index d10ec26ed..d82f076b7 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/NotificationMappingController.java +++ b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/NotificationMappingController.java @@ -42,7 +42,7 @@ public void handleSubscriptionMessage(NotificationStatus status) { @NotificationMessageMapping public void handleNotificationMessage(@NotificationSubject String subject, @NotificationMessage String message) { LOGGER.info("NotificationMessageMapping message is: {}", message); - LOGGER.info("NotificationMessageMapping subject is: {}" + subject); + LOGGER.info("NotificationMessageMapping subject is: {}", subject); } @NotificationUnsubscribeConfirmationMapping diff --git a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/SpringSNSSample.java b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/SpringSNSSample.java index bf09fe2a3..34426ada9 100644 --- a/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/SpringSNSSample.java +++ b/spring-cloud-aws-samples/spring-cloud-aws-sns-sample/src/main/java/io/awspring/cloud/sns/sample/SpringSNSSample.java @@ -30,6 +30,8 @@ import org.springframework.messaging.support.GenericMessage; import org.springframework.messaging.support.MessageBuilder; +import static io.awspring.cloud.messaging.core.TopicMessageChannel.NOTIFICATION_SUBJECT_HEADER; + @SpringBootApplication public class SpringSNSSample { @@ -48,11 +50,11 @@ public static void main(String[] args) { @EventListener(ApplicationReadyEvent.class) public void sendMessage() { - this.notificationMessagingTemplate.send("snsSpring", - MessageBuilder.withPayload("Spring Cloud AWS SNS Sample!").build()); + this.notificationMessagingTemplate.send("snsSpring", MessageBuilder.withPayload("Spring Cloud AWS SNS Sample!") + .setHeader(NOTIFICATION_SUBJECT_HEADER, "Message subject").build()); } - @SqsListener("InfrastructureStack-spring-aws") + @SqsListener("SnsSampleAppStack-spring-aws") private void listenToMessage(GenericMessage message) { LOGGER.info("This is message you want to see: {}", message.getPayload()); }