From 4fe3b6447303af27546195a4c306fdb78e143692 Mon Sep 17 00:00:00 2001 From: Tomas Longo Date: Wed, 29 Apr 2026 12:07:31 +0200 Subject: [PATCH 1/3] Mark channel configs as encrypted Signed-off-by: Tomas Longo --- .../org/opensearch/commons/notifications/model/Chime.kt | 7 +++++-- .../commons/notifications/model/MicrosoftTeams.kt | 7 +++++-- .../org/opensearch/commons/notifications/model/Slack.kt | 7 +++++-- .../org/opensearch/commons/notifications/model/Webhook.kt | 7 +++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index ee1c5aa8..4b652ec8 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -21,12 +21,15 @@ import java.io.IOException * Data class representing Chime channel. */ data class Chime( - val url: String + val url: String, + val isEncrypted: Boolean = false ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - validateUrl(url) + if (!isEncrypted) { + validateUrl(url) + } } companion object { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt index 48e32f15..c7900fc1 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt @@ -21,12 +21,15 @@ import java.io.IOException * Data class representing MicrosoftTeams channel. */ data class MicrosoftTeams( - val url: String + val url: String, + val isEncrypted: Boolean = false ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - validateUrl(url) + if (!isEncrypted) { + validateUrl(url) + } } companion object { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index b4433b95..30524681 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -21,12 +21,15 @@ import java.io.IOException * Data class representing Slack channel. */ data class Slack( - val url: String + val url: String, + val isEncrypted: Boolean = false ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - validateUrl(url) + if (!isEncrypted) { + validateUrl(url) + } } companion object { diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index e48f29f4..d86b4f75 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -27,12 +27,15 @@ import java.io.IOException data class Webhook( val url: String, val headerParams: Map = mapOf(), - val method: HttpMethodType = HttpMethodType.POST + val method: HttpMethodType = HttpMethodType.POST, + val isEncrypted: Boolean = false ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - validateUrl(url) + if (!isEncrypted) { + validateUrl(url) + } } companion object { From c764168833d197fa9cc7ccc9cfe20c3100836527 Mon Sep 17 00:00:00 2001 From: Tomas Longo Date: Wed, 29 Apr 2026 14:20:56 +0200 Subject: [PATCH 2/3] Don't use a flag to check if url is encrypted. Check for prefix instead. Signed-off-by: Tomas Longo --- .../commons/notifications/model/Chime.kt | 5 ++--- .../notifications/model/MicrosoftTeams.kt | 5 ++--- .../commons/notifications/model/Slack.kt | 5 ++--- .../commons/notifications/model/Webhook.kt | 5 ++--- .../commons/notifications/model/ChimeTests.kt | 22 +++++++++++++++++++ .../model/MicrosoftTeamsTests.kt | 22 +++++++++++++++++++ .../commons/notifications/model/SlackTests.kt | 22 +++++++++++++++++++ .../notifications/model/WebhookTests.kt | 22 +++++++++++++++++++ 8 files changed, 96 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index 4b652ec8..eee36c9f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -21,13 +21,12 @@ import java.io.IOException * Data class representing Chime channel. */ data class Chime( - val url: String, - val isEncrypted: Boolean = false + val url: String ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!isEncrypted) { + if (!url.startsWith("enc:")) { validateUrl(url) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt index c7900fc1..a1cbbf43 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt @@ -21,13 +21,12 @@ import java.io.IOException * Data class representing MicrosoftTeams channel. */ data class MicrosoftTeams( - val url: String, - val isEncrypted: Boolean = false + val url: String ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!isEncrypted) { + if (!url.startsWith("enc:")) { validateUrl(url) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index 30524681..a4578c0b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -21,13 +21,12 @@ import java.io.IOException * Data class representing Slack channel. */ data class Slack( - val url: String, - val isEncrypted: Boolean = false + val url: String ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!isEncrypted) { + if (!url.startsWith("enc:")) { validateUrl(url) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index d86b4f75..9cff1476 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -27,13 +27,12 @@ import java.io.IOException data class Webhook( val url: String, val headerParams: Map = mapOf(), - val method: HttpMethodType = HttpMethodType.POST, - val isEncrypted: Boolean = false + val method: HttpMethodType = HttpMethodType.POST ) : BaseConfigData { init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!isEncrypted) { + if (!url.startsWith("enc:")) { validateUrl(url) } } diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt index 22cf4146..0f7179b4 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/ChimeTests.kt @@ -76,6 +76,28 @@ internal class ChimeTests { } } + @Test + fun `Chime should skip url validation when url starts with enc prefix`() { + val encryptedUrl = "enc:this-is-not-a-real-url" + val chime = Chime(encryptedUrl) + assertEquals(encryptedUrl, chime.url) + } + + @Test + fun `Chime with enc prefix should serialize and deserialize transport object`() { + val sampleChime = Chime("enc:some-encrypted-value") + val recreatedObject = recreateObject(sampleChime) { Chime(it) } + assertEquals(sampleChime, recreatedObject) + } + + @Test + fun `Chime with enc prefix should serialize and deserialize using json object`() { + val sampleChime = Chime("enc:some-encrypted-value") + val jsonString = getJsonString(sampleChime) + val recreatedObject = createObjectFromJsonString(jsonString) { Chime.parse(it) } + assertEquals(sampleChime, recreatedObject) + } + @Test fun `Chime should safely ignore extra field in json object`() { val sampleChime = Chime("https://domain.com/sample_url#1234567890") diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt index 910a5f8e..001bffb2 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeamsTests.kt @@ -76,6 +76,28 @@ internal class MicrosoftTeamsTests { } } + @Test + fun `Microsoft Teams should skip url validation when url starts with enc prefix`() { + val encryptedUrl = "enc:this-is-not-a-real-url" + val microsoftTeams = MicrosoftTeams(encryptedUrl) + assertEquals(encryptedUrl, microsoftTeams.url) + } + + @Test + fun `Microsoft Teams with enc prefix should serialize and deserialize transport object`() { + val sampleMicrosoftTeams = MicrosoftTeams("enc:some-encrypted-value") + val recreatedObject = recreateObject(sampleMicrosoftTeams) { MicrosoftTeams(it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } + + @Test + fun `Microsoft Teams with enc prefix should serialize and deserialize using json object`() { + val sampleMicrosoftTeams = MicrosoftTeams("enc:some-encrypted-value") + val jsonString = getJsonString(sampleMicrosoftTeams) + val recreatedObject = createObjectFromJsonString(jsonString) { MicrosoftTeams.parse(it) } + assertEquals(sampleMicrosoftTeams, recreatedObject) + } + @Test fun `Microsoft Teams should safely ignore extra field in json object`() { val sampleMicrosoftTeams = MicrosoftTeams("https://domain.com/sample_url#1234567890") diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt index e66de5a5..db36b6af 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SlackTests.kt @@ -76,6 +76,28 @@ internal class SlackTests { } } + @Test + fun `Slack should skip url validation when url starts with enc prefix`() { + val encryptedUrl = "enc:this-is-not-a-real-url" + val slack = Slack(encryptedUrl) + assertEquals(encryptedUrl, slack.url) + } + + @Test + fun `Slack with enc prefix should serialize and deserialize transport object`() { + val sampleSlack = Slack("enc:some-encrypted-value") + val recreatedObject = recreateObject(sampleSlack) { Slack(it) } + assertEquals(sampleSlack, recreatedObject) + } + + @Test + fun `Slack with enc prefix should serialize and deserialize using json object`() { + val sampleSlack = Slack("enc:some-encrypted-value") + val jsonString = getJsonString(sampleSlack) + val recreatedObject = createObjectFromJsonString(jsonString) { Slack.parse(it) } + assertEquals(sampleSlack, recreatedObject) + } + @Test fun `Slack should safely ignore extra field in json object`() { val sampleSlack = Slack("https://domain.com/sample_url#1234567890") diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt index 455f7ca4..ac39e26d 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/WebhookTests.kt @@ -92,6 +92,28 @@ internal class WebhookTests { } } + @Test + fun `Webhook should skip url validation when url starts with enc prefix`() { + val encryptedUrl = "enc:this-is-not-a-real-url" + val webhook = Webhook(encryptedUrl) + assertEquals(encryptedUrl, webhook.url) + } + + @Test + fun `Webhook with enc prefix should serialize and deserialize transport object`() { + val sampleWebhook = Webhook("enc:some-encrypted-value") + val recreatedObject = recreateObject(sampleWebhook) { Webhook(it) } + assertEquals(sampleWebhook, recreatedObject) + } + + @Test + fun `Webhook with enc prefix should serialize and deserialize using json object`() { + val sampleWebhook = Webhook("enc:some-encrypted-value") + val jsonString = getJsonString(sampleWebhook) + val recreatedObject = createObjectFromJsonString(jsonString) { Webhook.parse(it) } + assertEquals(sampleWebhook, recreatedObject) + } + @Test fun `Webhook should safely ignore extra field in json object`() { val sampleWebhook = Webhook("https://domain.com/sample_url#1234567890") From fd075d220747d9515d890d08941a6ea0ef1d7f25 Mon Sep 17 00:00:00 2001 From: Tomas Longo Date: Wed, 29 Apr 2026 14:47:14 +0200 Subject: [PATCH 3/3] Use constant Signed-off-by: Tomas Longo --- .../opensearch/commons/notifications/NotificationConstants.kt | 1 + .../kotlin/org/opensearch/commons/notifications/model/Chime.kt | 3 ++- .../opensearch/commons/notifications/model/MicrosoftTeams.kt | 3 ++- .../kotlin/org/opensearch/commons/notifications/model/Slack.kt | 3 ++- .../org/opensearch/commons/notifications/model/Webhook.kt | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 74fcc600..9c1f0573 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -69,4 +69,5 @@ object NotificationConstants { const val PLUGIN_FEATURES_TAG = "plugin_features" const val DEFAULT_MAX_ITEMS = 1000 + const val ENCRYPTION_PREFIX = "enc:" } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt index eee36c9f..0e0c80ee 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Chime.kt @@ -4,6 +4,7 @@ */ package org.opensearch.commons.notifications.model +import org.opensearch.commons.notifications.NotificationConstants.ENCRYPTION_PREFIX import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl @@ -26,7 +27,7 @@ data class Chime( init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!url.startsWith("enc:")) { + if (!url.startsWith(ENCRYPTION_PREFIX)) { validateUrl(url) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt index a1cbbf43..2391d463 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/MicrosoftTeams.kt @@ -4,6 +4,7 @@ */ package org.opensearch.commons.notifications.model +import org.opensearch.commons.notifications.NotificationConstants.ENCRYPTION_PREFIX import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl @@ -26,7 +27,7 @@ data class MicrosoftTeams( init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!url.startsWith("enc:")) { + if (!url.startsWith(ENCRYPTION_PREFIX)) { validateUrl(url) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt index a4578c0b..b63e2f07 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Slack.kt @@ -4,6 +4,7 @@ */ package org.opensearch.commons.notifications.model +import org.opensearch.commons.notifications.NotificationConstants.ENCRYPTION_PREFIX import org.opensearch.commons.notifications.NotificationConstants.URL_TAG import org.opensearch.commons.utils.logger import org.opensearch.commons.utils.validateUrl @@ -26,7 +27,7 @@ data class Slack( init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!url.startsWith("enc:")) { + if (!url.startsWith(ENCRYPTION_PREFIX)) { validateUrl(url) } } diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt index 9cff1476..83f0b37f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/Webhook.kt @@ -4,6 +4,7 @@ */ package org.opensearch.commons.notifications.model +import org.opensearch.commons.notifications.NotificationConstants.ENCRYPTION_PREFIX import org.opensearch.commons.notifications.NotificationConstants.HEADER_PARAMS_TAG import org.opensearch.commons.notifications.NotificationConstants.METHOD_TAG import org.opensearch.commons.notifications.NotificationConstants.URL_TAG @@ -32,7 +33,7 @@ data class Webhook( init { require(!Strings.isNullOrEmpty(url)) { "URL is null or empty" } - if (!url.startsWith("enc:")) { + if (!url.startsWith(ENCRYPTION_PREFIX)) { validateUrl(url) } }