-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
28e688b
commit b18a489
Showing
7 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
package org.opensearch.commons.notifications.model | ||
|
||
import org.opensearch.common.io.stream.StreamInput | ||
import org.opensearch.common.io.stream.StreamOutput | ||
import org.opensearch.common.io.stream.Writeable | ||
import org.opensearch.common.xcontent.ToXContent | ||
import org.opensearch.common.xcontent.XContentBuilder | ||
import org.opensearch.common.xcontent.XContentParser | ||
import org.opensearch.common.xcontent.XContentParserUtils | ||
import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_FIELD | ||
import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_FIELD | ||
import org.opensearch.commons.utils.fieldIfNotNull | ||
import org.opensearch.commons.utils.logger | ||
import org.opensearch.commons.utils.validateIAMRoleArn | ||
import java.io.IOException | ||
import java.util.regex.Pattern | ||
|
||
/** | ||
* SNS notification data model | ||
*/ | ||
data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { | ||
|
||
init { | ||
require(SNS_ARN_REGEX.matcher(topicARN).find()) { "Invalid AWS SNS topic ARN: $topicARN" } | ||
if (roleARN != null) { | ||
validateIAMRoleArn(roleARN) | ||
} | ||
} | ||
|
||
override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { | ||
return builder.startObject() | ||
.field(TOPIC_ARN_FIELD, topicARN) | ||
.fieldIfNotNull(ROLE_ARN_FIELD, roleARN) | ||
.endObject() | ||
} | ||
|
||
/** | ||
* Constructor used in transport action communication. | ||
* @param input StreamInput stream to deserialize data from. | ||
*/ | ||
constructor(input: StreamInput) : this( | ||
topicARN = input.readString(), | ||
roleARN = input.readOptionalString() | ||
) | ||
|
||
@Throws(IOException::class) | ||
override fun writeTo(out: StreamOutput) { | ||
out.writeString(topicARN) | ||
out.writeOptionalString(roleARN) | ||
} | ||
|
||
companion object { | ||
private val log by logger(SNS::class.java) | ||
|
||
private val SNS_ARN_REGEX = | ||
Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") | ||
|
||
/** | ||
* reader to create instance of class from writable. | ||
*/ | ||
val reader = Writeable.Reader { SNS(it) } | ||
|
||
/** | ||
* Parser to parse xContent | ||
*/ | ||
val xParser = XParser { parse(it) } | ||
|
||
@JvmStatic | ||
@Throws(IOException::class) | ||
fun parse(xcp: XContentParser): SNS { | ||
var topicARN: String? = null | ||
var roleARN: String? = null | ||
|
||
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) | ||
while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { | ||
val fieldName = xcp.currentName() | ||
xcp.nextToken() | ||
when (fieldName) { | ||
TOPIC_ARN_FIELD -> topicARN = xcp.textOrNull() | ||
ROLE_ARN_FIELD -> roleARN = xcp.textOrNull() | ||
else -> { | ||
xcp.skipChildren() | ||
log.info("Unexpected field: $fieldName, while parsing SNS destination") | ||
} | ||
} | ||
} | ||
topicARN ?: throw IllegalArgumentException("$TOPIC_ARN_FIELD field absent") | ||
return SNS(topicARN, roleARN) | ||
} | ||
|
||
@JvmStatic | ||
@Throws(IOException::class) | ||
fun readFrom(sin: StreamInput): SNS? { | ||
return if (sin.readBoolean()) { | ||
SNS( | ||
topicARN = sin.readString(), | ||
roleARN = sin.readOptionalString() | ||
) | ||
} else null | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.commons.notifications.model | ||
|
||
import com.fasterxml.jackson.core.JsonParseException | ||
import org.junit.jupiter.api.Assertions | ||
import org.junit.jupiter.api.Assertions.assertThrows | ||
import org.junit.jupiter.api.Test | ||
import org.opensearch.commons.utils.createObjectFromJsonString | ||
import org.opensearch.commons.utils.getJsonString | ||
import org.opensearch.commons.utils.recreateObject | ||
|
||
internal class SNSTests { | ||
|
||
@Test | ||
fun `SNS should throw exception if empty topic`() { | ||
assertThrows(IllegalArgumentException::class.java) { | ||
SNS("", null) | ||
} | ||
val jsonString = "{\"topic_arn\":\"\"}" | ||
assertThrows(IllegalArgumentException::class.java) { | ||
createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
} | ||
} | ||
|
||
@Test | ||
fun `SNS should throw exception if invalid topic ARN`() { | ||
assertThrows(IllegalArgumentException::class.java) { | ||
SNS("arn:aws:es:us-east-1:012345678989:test", null) | ||
} | ||
val jsonString = "{\"topic_arn\":\"arn:aws:es:us-east-1:012345678989:test\"}" | ||
assertThrows(IllegalArgumentException::class.java) { | ||
createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
} | ||
} | ||
|
||
@Test | ||
fun `SNS should throw exception if invalid role ARN`() { | ||
assertThrows(IllegalArgumentException::class.java) { | ||
SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") | ||
} | ||
val jsonString = | ||
"{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" | ||
assertThrows(IllegalArgumentException::class.java) { | ||
createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
} | ||
} | ||
|
||
@Test | ||
fun `SNS serialize and deserialize transport object should be equal`() { | ||
val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") | ||
val recreatedObject = recreateObject(sampleSNS) { SNS(it) } | ||
Assertions.assertEquals(sampleSNS, recreatedObject) | ||
} | ||
|
||
@Test | ||
fun `SNS serialize and deserialize using json object should be equal`() { | ||
val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") | ||
val jsonString = getJsonString(sampleSNS) | ||
val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
Assertions.assertEquals(sampleSNS, recreatedObject) | ||
} | ||
|
||
@Test | ||
fun `SNS should deserialize json object using parser`() { | ||
val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") | ||
val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" | ||
val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
Assertions.assertEquals(sampleSNS, recreatedObject) | ||
} | ||
|
||
@Test | ||
fun `SNS should throw exception when invalid json object is passed`() { | ||
val jsonString = "sample message" | ||
assertThrows(JsonParseException::class.java) { | ||
createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
} | ||
} | ||
|
||
@Test | ||
fun `SNS should throw exception when arn is replace with arn2 in json object`() { | ||
val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") | ||
val jsonString = "{\"topic_arn2\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" | ||
assertThrows(IllegalArgumentException::class.java) { | ||
createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
} | ||
} | ||
|
||
@Test | ||
fun `SNS should safely ignore extra field in json object`() { | ||
val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", null) | ||
val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\", \"another\":\"field\"}" | ||
val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } | ||
Assertions.assertEquals(sampleSNS, recreatedObject) | ||
} | ||
} |