From 2fc8175a0fc09087c7e58685e3c33b94cdfe8a1c Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 16:18:34 -0700 Subject: [PATCH] Added legacy support for SNS messages. (#225) (#269) * Added legacy support for SNS messages. Signed-off-by: AWSHurneyt * Added license header to new classes. Signed-off-by: AWSHurneyt * Fixed style errors. Signed-off-by: AWSHurneyt Signed-off-by: AWSHurneyt (cherry picked from commit 9c2621eb5a2089c99ded4e1d205a362b71b4439c) Co-authored-by: AWSHurneyt Signed-off-by: AWSHurneyt --- .../message/LegacyDestinationType.java | 3 +- .../destination/message/LegacySNSMessage.java | 156 ++++++++++++++++++ .../commons/destination/util/Util.java | 35 ++++ .../LegacyPublishNotificationRequest.kt | 2 + .../message/LegacySNSMessageTest.java | 91 ++++++++++ .../commons/destination/util/UtilTest.java | 65 ++++++++ 6 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java create mode 100644 src/main/java/org/opensearch/commons/destination/util/Util.java create mode 100644 src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java create mode 100644 src/test/java/org/opensearch/commons/destination/util/UtilTest.java diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java index f5086c27..cf4071aa 100644 --- a/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java +++ b/src/main/java/org/opensearch/commons/destination/message/LegacyDestinationType.java @@ -12,5 +12,6 @@ public enum LegacyDestinationType { LEGACY_CHIME, LEGACY_SLACK, LEGACY_CUSTOM_WEBHOOK, - LEGACY_EMAIL + LEGACY_EMAIL, + LEGACY_SNS } diff --git a/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java new file mode 100644 index 00000000..0476f74e --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/message/LegacySNSMessage.java @@ -0,0 +1,156 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.message; + +import java.io.IOException; + +import org.opensearch.common.Strings; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.commons.destination.util.Util; + +/** + * This class holds the content of an SNS message + */ +public class LegacySNSMessage extends LegacyBaseMessage { + + private final String subject; + private final String message; + private final String roleArn; + private final String topicArn; + private final String clusterName; + + private LegacySNSMessage( + final String destinationName, + final String roleArn, + final String topicArn, + final String clusterName, + final String subject, + final String message + ) { + super(LegacyDestinationType.LEGACY_SNS, destinationName, message); + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + if (Strings.isNullOrEmpty(roleArn) || !Util.isValidIAMArn(roleArn)) { + throw new IllegalArgumentException("Role arn is missing/invalid: " + roleArn); + } + + if (Strings.isNullOrEmpty(topicArn) || !Util.isValidSNSArn(topicArn)) { + throw new IllegalArgumentException("Topic arn is missing/invalid: " + topicArn); + } + + if (Strings.isNullOrEmpty(message)) { + throw new IllegalArgumentException("Message content is missing"); + } + + this.subject = subject; + this.message = message; + this.roleArn = roleArn; + this.topicArn = topicArn; + this.clusterName = clusterName; + } + + public LegacySNSMessage(StreamInput streamInput) throws java.io.IOException { + super(streamInput); + this.subject = streamInput.readString(); + this.message = super.getMessageContent(); + this.roleArn = streamInput.readString(); + this.topicArn = streamInput.readString(); + this.clusterName = streamInput.readString(); + } + + @Override + public String toString() { + return "DestinationType: " + + getChannelType() + + ", DestinationName: " + + destinationName + + ", RoleARn: " + + roleArn + + ", TopicArn: " + + topicArn + + ", ClusterName: " + + clusterName + + ", Subject: " + + subject + + ", Message: " + + message; + } + + public static class Builder { + private final String destinationName; + private String subject; + private String message; + private String roleArn; + private String topicArn; + private String clusterName; + + public Builder(String destinationName) { + this.destinationName = destinationName; + } + + public Builder withSubject(String subject) { + this.subject = subject; + return this; + } + + public Builder withMessage(String message) { + this.message = message; + return this; + } + + public Builder withRole(String roleArn) { + this.roleArn = roleArn; + return this; + } + + public Builder withTopicArn(String topicArn) { + this.topicArn = topicArn; + return this; + } + + public Builder withClusterName(String clusterName) { + this.clusterName = clusterName; + return this; + } + + public LegacySNSMessage build() { + return new LegacySNSMessage(this.destinationName, this.roleArn, this.topicArn, this.clusterName, this.subject, this.message); + } + } + + public String getSubject() { + return subject; + } + + public String getMessage() { + return message; + } + + public String getRoleArn() { + return roleArn; + } + + public String getTopicArn() { + return topicArn; + } + + public String getClusterName() { + return clusterName; + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + super.writeTo(streamOutput); + streamOutput.writeString(subject); + streamOutput.writeString(message); + streamOutput.writeString(roleArn); + streamOutput.writeString(topicArn); + streamOutput.writeString(clusterName); + } +} diff --git a/src/main/java/org/opensearch/commons/destination/util/Util.java b/src/main/java/org/opensearch/commons/destination/util/Util.java new file mode 100644 index 00000000..9cb65ccb --- /dev/null +++ b/src/main/java/org/opensearch/commons/destination/util/Util.java @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.util; + +import java.util.regex.Pattern; + +import org.opensearch.common.Strings; +import org.opensearch.common.ValidationException; + +public class Util { + private Util() {} + + public static final Pattern SNS_ARN_REGEX = Pattern + .compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)(\\.fifo)?$"); + public static final Pattern IAM_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z0-9-/_+=@.,]+)$"); + + public static String getRegion(String arn) { + // sample topic arn arn:aws:sns:us-west-2:075315751589:test-notification + if (isValidSNSArn(arn)) { + return arn.split(":")[3]; + } + throw new IllegalArgumentException("Unable to retrieve region from ARN " + arn); + } + + public static boolean isValidIAMArn(String arn) { + return Strings.hasLength(arn) && IAM_ARN_REGEX.matcher(arn).find(); + } + + public static boolean isValidSNSArn(String arn) throws ValidationException { + return Strings.hasLength(arn) && SNS_ARN_REGEX.matcher(arn).find(); + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt index 211e2076..a4f8024d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/action/LegacyPublishNotificationRequest.kt @@ -15,6 +15,7 @@ import org.opensearch.commons.destination.message.LegacyChimeMessage import org.opensearch.commons.destination.message.LegacyCustomWebhookMessage import org.opensearch.commons.destination.message.LegacyDestinationType import org.opensearch.commons.destination.message.LegacyEmailMessage +import org.opensearch.commons.destination.message.LegacySNSMessage import org.opensearch.commons.destination.message.LegacySlackMessage import java.io.IOException @@ -52,6 +53,7 @@ class LegacyPublishNotificationRequest : ActionRequest { LegacyDestinationType.LEGACY_CUSTOM_WEBHOOK -> LegacyCustomWebhookMessage(input) LegacyDestinationType.LEGACY_SLACK -> LegacySlackMessage(input) LegacyDestinationType.LEGACY_EMAIL -> LegacyEmailMessage(input) + LegacyDestinationType.LEGACY_SNS -> LegacySNSMessage(input) } } diff --git a/src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java b/src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java new file mode 100644 index 00000000..a9fd1dd3 --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/message/LegacySNSMessageTest.java @@ -0,0 +1,91 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.message; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class LegacySNSMessageTest { + + @Test + public void testCreateRoleArnMissingMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms").withMessage("dummyMessage").build(); + } catch (Exception ex) { + assertEquals("Role arn is missing/invalid: null", ex.getMessage()); + throw ex; + } + } + + @Test + public void testCreateTopicArnMissingMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withMessage("dummyMessage") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .build(); + } catch (Exception ex) { + assertEquals("Topic arn is missing/invalid: null", ex.getMessage()); + throw ex; + } + } + + @Test + public void testCreateContentMissingMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + } catch (Exception ex) { + assertEquals("Message content is missing", ex.getMessage()); + throw ex; + } + } + + @Test + public void testInValidRoleMessage() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withMessage("dummyMessage") + .withRole("dummyRole") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + } catch (Exception ex) { + assertEquals("Role arn is missing/invalid: dummyRole", ex.getMessage()); + throw ex; + } + } + + @Test + public void testValidMessage() { + LegacySNSMessage message = new LegacySNSMessage.Builder("sms") + .withMessage("dummyMessage") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + assertEquals(LegacyDestinationType.LEGACY_SNS, message.getChannelType()); + assertEquals("sms", message.getChannelName()); + assertEquals("dummyMessage", message.getMessage()); + assertEquals("arn:aws:iam::853806060000:role/domain/abc", message.getRoleArn()); + assertEquals("arn:aws:sns:us-west-2:475313751589:test-notification", message.getTopicArn()); + } + + @Test + public void testInValidChannelName() { + try { + LegacySNSMessage message = new LegacySNSMessage.Builder("") + .withMessage("dummyMessage") + .withRole("arn:aws:iam::853806060000:role/domain/abc") + .withTopicArn("arn:aws:sns:us-west-2:475313751589:test-notification") + .build(); + } catch (Exception ex) { + assertEquals("Channel name must be defined", ex.getMessage()); + throw ex; + } + } +} diff --git a/src/test/java/org/opensearch/commons/destination/util/UtilTest.java b/src/test/java/org/opensearch/commons/destination/util/UtilTest.java new file mode 100644 index 00000000..506beb42 --- /dev/null +++ b/src/test/java/org/opensearch/commons/destination/util/UtilTest.java @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.commons.destination.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class UtilTest { + + @Test + public void testValidSNSTopicArn() { + String topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification"; + assertTrue("topic arn should be valid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws-cn:sns:us-west-2:475313751589:test-notification"; + assertTrue("topic arn should be valid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws-cn:sns:us-west-2:475313751589:test-notification.fifo"; + assertTrue("topic arn should be valid", Util.isValidSNSArn(topicArn)); + } + + @Test + public void testInvalidSNSTopicArn() { + String topicArn = "arn:aws:sns1:us-west-2:475313751589:test-notification"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification.fifo.fifo"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification.fi"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notifica.tion"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification&fifo"; + assertFalse("topic arn should be Invalid", Util.isValidSNSArn(topicArn)); + } + + @Test + public void testIAMRoleArn() { + String roleArn = "arn:aws:iam::853806060000:role/domain/abc"; + assertTrue("IAM role arn should be valid", Util.isValidIAMArn(roleArn)); + roleArn = "arn:aws:iam::853806060000:role/domain/a@+=.,-_bc"; + assertTrue("IAM role arn should be valid", Util.isValidIAMArn(roleArn)); + } + + @Test + public void testInvalidIAMRoleArn() { + String roleArn = "arn:aws:iam::85380606000000000:role/domain/010-asdf"; + assertFalse("IAM role arn should be Invalid", Util.isValidIAMArn(roleArn)); + } + + @Test + public void testGetRegion() { + String topicArn = "arn:aws:sns:us-west-2:475313751589:test-notification"; + assertEquals(Util.getRegion(topicArn), "us-west-2"); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidGetRegion() { + String topicArn = "arn:aws:abs:us-west-2:475313751589:test-notification"; + assertEquals(Util.getRegion(topicArn), "us-west-2"); + } +}