diff --git a/notifications/notifications/src/main/kotlin/org/opensearch/notifications/security/UserAccessManager.kt b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/security/UserAccessManager.kt index 59e6976fdd3..ecb1a1cd407 100644 --- a/notifications/notifications/src/main/kotlin/org/opensearch/notifications/security/UserAccessManager.kt +++ b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/security/UserAccessManager.kt @@ -8,6 +8,7 @@ package org.opensearch.notifications.security import org.opensearch.OpenSearchStatusException import org.opensearch.commons.authuser.User import org.opensearch.core.rest.RestStatus +import org.opensearch.notifications.settings.FilterByBackendRolesAccessStrategy import org.opensearch.notifications.settings.PluginSettings /** @@ -48,6 +49,22 @@ internal object UserAccessManager : UserAccess { return user.backendRoles } + fun checkUserBackendRolesAccess(userBackendRoles: List, objectAccess: List): Boolean { + val filterByAccessStrategy = PluginSettings.getFilterByBackendAccessStrategy() + if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.ALL.strategy) { + return userBackendRoles.containsAll(objectAccess) + } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.INTERSECT.strategy) { + return userBackendRoles.any { it in objectAccess } + } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.EXACT.strategy) { + return userBackendRoles.sorted().equals(objectAccess.sorted()) + } + // Not sure if this is necessary, since there is a validator + // on the setting itself + throw IllegalArgumentException( + "Invalid filter by access strategy: $filterByAccessStrategy" + ) + } + /** * {@inheritDoc} */ @@ -57,6 +74,6 @@ internal object UserAccessManager : UserAccess { } // User has access to resource if resource is public i.e. no access roles attached, user is an admin user or there is any intersection // between user backend roles and access roles - return access.isEmpty() || user.roles.contains(ADMIN_ROLE) || user.backendRoles.any { it in access } + return access.isEmpty() || user.roles.contains(ADMIN_ROLE) || checkUserBackendRolesAccess(user.backendRoles, access) } } diff --git a/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategy.kt b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategy.kt new file mode 100644 index 00000000000..8f9b6b468d4 --- /dev/null +++ b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategy.kt @@ -0,0 +1,26 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.notifications.settings + +/** + * Defines the FilterByBackendRolesAccessStrategy + */ +internal enum class FilterByBackendRolesAccessStrategy(val strategy: String) { + /** + * User backend roles must contain all resource backend roles to have access + */ + ALL("all"), + + /** + * Backend roles must be exactly equal to have access + */ + EXACT("exact"), + + /** + * Backend roles must intersect to have access + */ + INTERSECT("intersect") +} diff --git a/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategyValidator.kt b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategyValidator.kt new file mode 100644 index 00000000000..b6be5596054 --- /dev/null +++ b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategyValidator.kt @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.notifications.settings + +import org.opensearch.common.settings.Setting + +class FilterByBackendRolesAccessStrategyValidator : Setting.Validator { + override fun validate(strategy: String) { + val allStrategies: List = FilterByBackendRolesAccessStrategy.entries.map { it.strategy } + + when (strategy) { + FilterByBackendRolesAccessStrategy.ALL.strategy, + FilterByBackendRolesAccessStrategy.EXACT.strategy, + FilterByBackendRolesAccessStrategy.INTERSECT.strategy -> {} + else -> throw IllegalArgumentException( + "Setting value must be one of [$allStrategies]" + ) + } + } +} diff --git a/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/PluginSettings.kt b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/PluginSettings.kt index ec4ac4996be..d1401bc9cb0 100644 --- a/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/PluginSettings.kt +++ b/notifications/notifications/src/main/kotlin/org/opensearch/notifications/settings/PluginSettings.kt @@ -65,6 +65,11 @@ internal object PluginSettings { */ private const val FILTER_BY_BACKEND_ROLES_KEY = "$GENERAL_KEY_PREFIX.filter_by_backend_roles" + /** + * Setting to control filtering by backend roles access strategy. + */ + private const val FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY_KEY = "$GENERAL_KEY_PREFIX.filter_by_backend_roles_access_strategy" + /** * Default operation timeout for network operations. */ @@ -85,6 +90,11 @@ internal object PluginSettings { */ private const val MINIMUM_ITEMS_QUERY_COUNT = 10 + /** + * Default filter by backend roles access strategy + */ + private val DEFAULT_FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY = FilterByBackendRolesAccessStrategy.INTERSECT.strategy + /** * Operation timeout setting in ms for I/O operations */ @@ -97,6 +107,12 @@ internal object PluginSettings { @Volatile var defaultItemsQueryCount: Int + /** + * Access strategy when filtering by backend roles + */ + @Volatile + var filterByBackendRolesAccessStrategy: String + private const val DECIMAL_RADIX: Int = 10 private val log by logger(javaClass) @@ -117,6 +133,8 @@ internal object PluginSettings { operationTimeoutMs = (settings?.get(OPERATION_TIMEOUT_MS_KEY)?.toLong()) ?: DEFAULT_OPERATION_TIMEOUT_MS defaultItemsQueryCount = (settings?.get(DEFAULT_ITEMS_QUERY_COUNT_KEY)?.toInt()) ?: DEFAULT_ITEMS_QUERY_COUNT_VALUE + filterByBackendRolesAccessStrategy = (settings?.get(FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY_KEY)) + ?: DEFAULT_FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY defaultSettings = mapOf( OPERATION_TIMEOUT_MS_KEY to operationTimeoutMs.toString(DECIMAL_RADIX), DEFAULT_ITEMS_QUERY_COUNT_KEY to defaultItemsQueryCount.toString(DECIMAL_RADIX) @@ -174,6 +192,14 @@ internal object PluginSettings { Setting.Property.Final ) + val FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY: Setting = Setting.simpleString( + FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY_KEY, + DEFAULT_FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY, + FilterByBackendRolesAccessStrategyValidator(), + NodeScope, + Dynamic + ) + /** This setting sets the remote metadata store type */ val REMOTE_METADATA_STORE_TYPE: Setting = Setting .simpleString( @@ -214,6 +240,10 @@ internal object PluginSettings { } } + fun getFilterByBackendAccessStrategy(): String { + return filterByBackendRolesAccessStrategy + } + /** * Returns list of additional settings available specific to this plugin. * @@ -225,6 +255,7 @@ internal object PluginSettings { DEFAULT_ITEMS_QUERY_COUNT, FILTER_BY_BACKEND_ROLES, MULTI_TENANCY_ENABLED, + FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY, REMOTE_METADATA_REGION, REMOTE_METADATA_ENDPOINT, REMOTE_METADATA_STORE_TYPE, @@ -239,6 +270,7 @@ internal object PluginSettings { private fun updateSettingValuesFromLocal(clusterService: ClusterService) { operationTimeoutMs = OPERATION_TIMEOUT_MS.get(clusterService.settings) defaultItemsQueryCount = DEFAULT_ITEMS_QUERY_COUNT.get(clusterService.settings) + filterByBackendRolesAccessStrategy = FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(clusterService.settings) } /** @@ -257,6 +289,11 @@ internal object PluginSettings { log.debug("$LOG_PREFIX:$DEFAULT_ITEMS_QUERY_COUNT_KEY -autoUpdatedTo-> $clusterDefaultItemsQueryCount") defaultItemsQueryCount = clusterDefaultItemsQueryCount } + val clusterFilterByAccessStrategy = clusterService.clusterSettings.get(FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) + if (clusterFilterByAccessStrategy != null) { + log.debug("$LOG_PREFIX:$FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY_KEY -autoUpdatedTo-> $clusterFilterByAccessStrategy") + filterByBackendRolesAccessStrategy = clusterFilterByAccessStrategy + } } /** @@ -278,6 +315,10 @@ internal object PluginSettings { defaultItemsQueryCount = it log.info("$LOG_PREFIX:$DEFAULT_ITEMS_QUERY_COUNT_KEY -updatedTo-> $it") } + clusterService.clusterSettings.addSettingsUpdateConsumer(FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) { + filterByBackendRolesAccessStrategy = it + log.info("$LOG_PREFIX:$FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY_KEY -updatedTo-> $it") + } } // reset the settings values to default values for testing purpose @@ -285,5 +326,6 @@ internal object PluginSettings { fun reset() { operationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS defaultItemsQueryCount = DEFAULT_ITEMS_QUERY_COUNT_VALUE + filterByBackendRolesAccessStrategy = DEFAULT_FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY } } diff --git a/notifications/notifications/src/test/kotlin/org/opensearch/integtest/PluginRestTestCase.kt b/notifications/notifications/src/test/kotlin/org/opensearch/integtest/PluginRestTestCase.kt index 5eca041385c..08048e255b1 100644 --- a/notifications/notifications/src/test/kotlin/org/opensearch/integtest/PluginRestTestCase.kt +++ b/notifications/notifications/src/test/kotlin/org/opensearch/integtest/PluginRestTestCase.kt @@ -258,10 +258,10 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() { user: String, password: String, role: String, - backendRole: String, + backendRoles: Array, clusterPermissions: String? ) { - createUser(user, password, arrayOf(backendRole)) + createUser(user, password, backendRoles) createCustomRole(role, clusterPermissions) createUserRolesMapping(role, arrayOf(user)) } diff --git a/notifications/notifications/src/test/kotlin/org/opensearch/integtest/SecurityNotificationIT.kt b/notifications/notifications/src/test/kotlin/org/opensearch/integtest/SecurityNotificationIT.kt index fd1854513a9..9d74bd6b7a6 100644 --- a/notifications/notifications/src/test/kotlin/org/opensearch/integtest/SecurityNotificationIT.kt +++ b/notifications/notifications/src/test/kotlin/org/opensearch/integtest/SecurityNotificationIT.kt @@ -11,11 +11,16 @@ import org.junit.Before import org.junit.BeforeClass import org.opensearch.client.RestClient import org.opensearch.commons.notifications.model.ConfigType +import org.opensearch.commons.notifications.model.MethodType import org.opensearch.commons.notifications.model.NotificationConfig import org.opensearch.commons.notifications.model.Slack +import org.opensearch.commons.notifications.model.SmtpAccount import org.opensearch.commons.rest.SecureRestClientBuilder import org.opensearch.core.rest.RestStatus import org.opensearch.notifications.NotificationPlugin +import org.opensearch.notifications.getJsonString +import org.opensearch.notifications.settings.FilterByBackendRolesAccessStrategy +import org.opensearch.notifications.settings.PluginSettings import org.opensearch.notifications.verifyChannelIdEquals import org.opensearch.notifications.verifySingleConfigEquals import org.opensearch.rest.RestRequest @@ -51,7 +56,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test Create slack notification config with user that has create Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) // Create sample config request reference val sampleSlack = Slack("https://hooks.slack.com/services/sample_slack_url") @@ -94,7 +99,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test Create slack notification config without create Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) // Create sample config request reference val sampleSlack = Slack("https://hooks.slack.com/services/sample_slack_url") @@ -130,7 +135,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test update slack notification config with user that has create Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_UPDATE_CONFIG_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_UPDATE_CONFIG_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_UPDATE_CONFIG_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_UPDATE_CONFIG_ACCESS]) // Create sample config request reference val sampleSlack = Slack("https://hooks.slack.com/services/sample_slack_url") @@ -207,7 +212,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test update slack notification config without create Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) // Create sample config request reference val sampleSlack = Slack("https://hooks.slack.com/services/sample_slack_url") @@ -243,7 +248,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test get slack notification config with user that has get Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_GET_CONFIG_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) // Create sample config request reference val sampleSlack = Slack("https://hooks.slack.com/services/sample_slack_url") @@ -284,7 +289,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test get slack notification config without get Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) // Get Slack notification config @@ -299,7 +304,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test delete slack notification config with user that has get Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_DELETE_CONFIG_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_DELETE_CONFIG_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_DELETE_CONFIG_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_DELETE_CONFIG_ACCESS]) // Create sample config request reference val sampleSlack = Slack("https://hooks.slack.com/services/sample_slack_url") @@ -342,7 +347,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test delete slack notification config without get Notification permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) // Get Slack notification config @@ -357,7 +362,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test getChannelList should return only channels with get channel permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_GET_CHANNEL_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CHANNEL_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_GET_CHANNEL_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CHANNEL_ACCESS]) val slackId = createConfig(configType = ConfigType.SLACK) val chimeId = createConfig(configType = ConfigType.CHIME) @@ -387,7 +392,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test getChannelList fails without get channel permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) createConfig(configType = ConfigType.SLACK) Thread.sleep(1000) @@ -404,7 +409,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test Get plugin features should return non-empty configTypes with get features permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_GET_PLUGIN_FEATURE_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_PLUGIN_FEATURE_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_GET_PLUGIN_FEATURE_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_PLUGIN_FEATURE_ACCESS]) val getResponse = executeRequest( RestRequest.Method.GET.name, @@ -420,7 +425,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test Get plugin features fails without get features permission`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) executeRequest( RestRequest.Method.GET.name, @@ -433,7 +438,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test send test slack message with send permissions`() { - createUserWithCustomRole(user, password, NOTIFICATION_TEST_SEND_ACCESS, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_TEST_SEND_ACCESS]) + createUserWithCustomRole(user, password, NOTIFICATION_TEST_SEND_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_TEST_SEND_ACCESS]) // Create webhook notification config val createRequestJsonString = """ @@ -471,7 +476,7 @@ class SecurityNotificationIT : PluginRestTestCase() { } fun `test send test slack message without send permissions`() { - createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, "", ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) + createUserWithCustomRole(user, password, NOTIFICATION_NO_ACCESS_ROLE, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_NO_ACCESS_ROLE]) // Create webhook notification config val createRequestJsonString = """ @@ -502,4 +507,402 @@ class SecurityNotificationIT : PluginRestTestCase() { deleteUserWithCustomRole(user, NOTIFICATION_NO_ACCESS_ROLE) } + + fun `test create smtp sender config with user that has create Notification permission`() { + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf(""), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.OK.status + ) + verifySingleConfigEquals(configId, referenceObject, getConfigResponse) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + } + } + + fun `test get smtp sender has access with filter by backend roles enabled`() { + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES.key, true)) + + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf("role1"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + + val getUser = "getUser" + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + createUserWithCustomRole(getUser, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf("role1"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.OK.status, + getUserClient + ) + verifySingleConfigEquals(configId, referenceObject, getConfigResponse) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + deleteUserWithCustomRole(getUser, NOTIFICATION_GET_CONFIG_ACCESS) + getUserClient?.close() + } + } + + fun `test get smtp sender does not have access with filter by backend roles enabled`() { + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES.key, true)) + + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf("role1"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + + val getUser = "getUser" + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + createUserWithCustomRole(getUser, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf("role2"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.FORBIDDEN.status, + getUserClient + ) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + deleteUserWithCustomRole(getUser, NOTIFICATION_GET_CONFIG_ACCESS) + getUserClient?.close() + } + } + + fun `test get smtp sender has access when filter by backend access strategy is intersect`() { + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES.key, true)) + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.key, FilterByBackendRolesAccessStrategy.INTERSECT.strategy)) + + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf("role1", "role2"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + + val getUser = "getUser" + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + createUserWithCustomRole(getUser, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf("role2", "role3"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.OK.status, + getUserClient + ) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + deleteUserWithCustomRole(getUser, NOTIFICATION_GET_CONFIG_ACCESS) + getUserClient?.close() + } + } + + fun `test get smtp sender has access when filter by backend access strategy is exact`() { + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES.key, true)) + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.key, FilterByBackendRolesAccessStrategy.EXACT.strategy)) + + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf("role1", "role2"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + + val getUser = "getUser" + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + createUserWithCustomRole(getUser, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf("role1", "role2"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.OK.status, + getUserClient + ) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + deleteUserWithCustomRole(getUser, NOTIFICATION_GET_CONFIG_ACCESS) + getUserClient?.close() + } + } + + fun `test get smtp sender has access when filter by backend access strategy is exact and backend roles order is different`() { + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES.key, true)) + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.key, FilterByBackendRolesAccessStrategy.EXACT.strategy)) + + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf("role1", "role2"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + + val getUser = "getUser" + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + createUserWithCustomRole(getUser, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf("role2", "role1"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.OK.status, + getUserClient + ) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + deleteUserWithCustomRole(getUser, NOTIFICATION_GET_CONFIG_ACCESS) + getUserClient?.close() + } + } + + fun `test get smtp sender has access when filter by backend access strategy is all`() { + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES.key, true)) + updateClusterSettings(ClusterSetting("persistent", PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.key, FilterByBackendRolesAccessStrategy.ALL.strategy)) + + createUserWithCustomRole(user, password, NOTIFICATION_CREATE_CONFIG_ACCESS, arrayOf("role1", "role2"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_CREATE_CONFIG_ACCESS]) + + // Create sample config request reference + val sampleSmtpAccount = SmtpAccount("example-host", 2465, MethodType.SSL, "no-reply@fake-host.com") + val referenceObject = NotificationConfig( + "this is a sample config name", + "this is a sample config description", + ConfigType.SMTP_ACCOUNT, + isEnabled = true, + configData = sampleSmtpAccount + ) + + val sampleSmtpJsonString = getJsonString(sampleSmtpAccount) + + // Create SMTP account config + val createRequestJsonString = """ + { + "config":{ + "name":"${referenceObject.name}", + "description":"${referenceObject.description}", + "config_type":"smtp_account", + "is_enabled":${referenceObject.isEnabled}, + "smtp_account":$sampleSmtpJsonString + } + } + """.trimIndent() + + val getUser = "getUser" + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val configId = createConfigWithRequestJsonString(createRequestJsonString, userClient!!) + Assert.assertNotNull(configId) + Thread.sleep(1000) + + createUserWithCustomRole(getUser, password, NOTIFICATION_GET_CONFIG_ACCESS, arrayOf("role2", "role1", "role3"), ROLE_TO_PERMISSION_MAPPING[NOTIFICATION_GET_CONFIG_ACCESS]) + + // Get SMTP account config + val getConfigResponse = executeRequest( + RestRequest.Method.GET.name, + "${NotificationPlugin.PLUGIN_BASE_URI}/configs/$configId", + "", + RestStatus.OK.status, + getUserClient + ) + } finally { + deleteUserWithCustomRole(user, NOTIFICATION_CREATE_CONFIG_ACCESS) + deleteUserWithCustomRole(getUser, NOTIFICATION_GET_CONFIG_ACCESS) + getUserClient?.close() + } + } } diff --git a/notifications/notifications/src/test/kotlin/org/opensearch/notifications/security/UserAccessManagerTests.kt b/notifications/notifications/src/test/kotlin/org/opensearch/notifications/security/UserAccessManagerTests.kt new file mode 100644 index 00000000000..4fe6225cb98 --- /dev/null +++ b/notifications/notifications/src/test/kotlin/org/opensearch/notifications/security/UserAccessManagerTests.kt @@ -0,0 +1,223 @@ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.notifications.security + +import com.nhaarman.mockitokotlin2.whenever +import org.junit.Assert +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.mock +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.settings.ClusterSettings +import org.opensearch.common.settings.Settings +import org.opensearch.notifications.settings.FilterByBackendRolesAccessStrategy +import org.opensearch.notifications.settings.PluginSettings + +internal class UserAccessManagerTests { + private lateinit var clusterService: ClusterService + + private val keyPrefix = "opensearch.notifications" + private val generalKeyPrefix = "$keyPrefix.general" + private val filterByBackendRolesAccessStrategyKey = "$generalKeyPrefix.filter_by_backend_roles_access_strategy" + + private val defaultSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.INTERSECT.strategy) + .build() + + @BeforeEach + fun setup() { + clusterService = mock(ClusterService::class.java, "clusterService") + } + + @AfterEach + fun reset() { + PluginSettings.reset() + } + + @Test + fun `checkUserBackendRolesAccess strategy is intersect and roles intersect`() { + Assert.assertTrue( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role1", "role2"), + listOf("role2", "role3") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is intersect and roles do not intersect`() { + Assert.assertFalse( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role1"), + listOf("role2") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is exact and roles intersect but are not the same`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.EXACT.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + + Assert.assertFalse( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role1", "role2"), + listOf("role2", "role3") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is exact and roles are the same`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.EXACT.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + + Assert.assertTrue( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role1", "role2"), + listOf("role1", "role2") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is exact and roles are the same, but in different order`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.EXACT.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + + Assert.assertTrue( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role2", "role1"), + listOf("role1", "role2") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is all and user roles contain all object roles`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.ALL.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + + Assert.assertTrue( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role2", "role3", "role1"), + listOf("role1", "role2") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is all and user roles do not contain all object roles`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.ALL.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + + Assert.assertFalse( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role2", "role3"), + listOf("role1", "role2", "role3") + ) + ) + } + + @Test + fun `checkUserBackendRolesAccess strategy is all and user roles match object roles`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.ALL.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + + Assert.assertTrue( + UserAccessManager.checkUserBackendRolesAccess( + listOf("role2", "role3"), + listOf("role3", "role2") + ) + ) + } +} diff --git a/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt b/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt new file mode 100644 index 00000000000..55de7bc22d1 --- /dev/null +++ b/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt @@ -0,0 +1,29 @@ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.notifications.settings + +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test + +internal class FilterByBackendRolesAccessStrategyValidatorTests { + @Test + fun `accepts valid values`() { + val validator = FilterByBackendRolesAccessStrategyValidator() + validator.validate(FilterByBackendRolesAccessStrategy.ALL.strategy) + validator.validate(FilterByBackendRolesAccessStrategy.EXACT.strategy) + validator.validate(FilterByBackendRolesAccessStrategy.INTERSECT.strategy) + } + + @Test + fun `rejects invalid value`() { + val validator = FilterByBackendRolesAccessStrategyValidator() + + assertThrows(IllegalArgumentException::class.java) { + validator.validate("foo") + } + } +} diff --git a/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/PluginSettingsTests.kt b/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/PluginSettingsTests.kt index d58c9471bc3..fabb0127e4b 100644 --- a/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/PluginSettingsTests.kt +++ b/notifications/notifications/src/test/kotlin/org/opensearch/notifications/settings/PluginSettingsTests.kt @@ -29,11 +29,13 @@ internal class PluginSettingsTests { private val alertingFilterByBackendRolesKey = "plugins.alerting.filter_by_backend_roles" private val filterByBackendRolesKey = "$generalKeyPrefix.filter_by_backend_roles" private val multiTenancyEnabledKey = "plugins.notifications.multi_tenancy_enabled" + private val filterByBackendRolesAccessStrategyKey = "$generalKeyPrefix.filter_by_backend_roles_access_strategy" private val defaultSettings = Settings.builder() .put(operationTimeoutKey, 60000L) .put(defaultItemQueryCountKey, 100L) .put(filterByBackendRolesKey, false) + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.INTERSECT.strategy) .build() @BeforeEach @@ -56,7 +58,8 @@ internal class PluginSettingsTests { listOf( PluginSettings.OPERATION_TIMEOUT_MS, PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, - PluginSettings.FILTER_BY_BACKEND_ROLES + PluginSettings.FILTER_BY_BACKEND_ROLES, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -65,6 +68,10 @@ internal class PluginSettingsTests { defaultSettings[defaultItemQueryCountKey], PluginSettings.defaultItemsQueryCount.toString() ) + Assertions.assertEquals( + FilterByBackendRolesAccessStrategy.INTERSECT.strategy, + PluginSettings.getFilterByBackendAccessStrategy() + ) } @Test @@ -80,7 +87,9 @@ internal class PluginSettingsTests { clusterSettings, setOf( PluginSettings.OPERATION_TIMEOUT_MS, - PluginSettings.DEFAULT_ITEMS_QUERY_COUNT + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -104,7 +113,8 @@ internal class PluginSettingsTests { clusterSettings, setOf( PluginSettings.OPERATION_TIMEOUT_MS, - PluginSettings.DEFAULT_ITEMS_QUERY_COUNT + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -132,7 +142,8 @@ internal class PluginSettingsTests { setOf( PluginSettings.OPERATION_TIMEOUT_MS, PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, - PluginSettings.FILTER_BY_BACKEND_ROLES + PluginSettings.FILTER_BY_BACKEND_ROLES, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -157,7 +168,8 @@ internal class PluginSettingsTests { PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, PluginSettings.LEGACY_ALERTING_FILTER_BY_BACKEND_ROLES, PluginSettings.ALERTING_FILTER_BY_BACKEND_ROLES, - PluginSettings.FILTER_BY_BACKEND_ROLES + PluginSettings.FILTER_BY_BACKEND_ROLES, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -181,7 +193,8 @@ internal class PluginSettingsTests { PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, PluginSettings.LEGACY_ALERTING_FILTER_BY_BACKEND_ROLES, PluginSettings.ALERTING_FILTER_BY_BACKEND_ROLES, - PluginSettings.FILTER_BY_BACKEND_ROLES + PluginSettings.FILTER_BY_BACKEND_ROLES, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -204,7 +217,8 @@ internal class PluginSettingsTests { PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, PluginSettings.LEGACY_ALERTING_FILTER_BY_BACKEND_ROLES, PluginSettings.ALERTING_FILTER_BY_BACKEND_ROLES, - PluginSettings.FILTER_BY_BACKEND_ROLES + PluginSettings.FILTER_BY_BACKEND_ROLES, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) ) ) @@ -238,4 +252,25 @@ internal class PluginSettingsTests { Assertions.assertTrue(PluginSettings.REMOTE_METADATA_REGION.key.startsWith("plugins.notifications.")) Assertions.assertTrue(PluginSettings.REMOTE_METADATA_SERVICE_NAME.key.startsWith("plugins.notifications.")) } + + @Test + fun `test filter by backend roles access strategy setting uses provided value`() { + val clusterSettings = Settings.builder() + .put(filterByBackendRolesAccessStrategyKey, FilterByBackendRolesAccessStrategy.ALL.strategy) + .build() + + whenever(clusterService.settings).thenReturn(defaultSettings) + whenever(clusterService.clusterSettings).thenReturn( + ClusterSettings( + clusterSettings, + setOf( + PluginSettings.OPERATION_TIMEOUT_MS, + PluginSettings.DEFAULT_ITEMS_QUERY_COUNT, + PluginSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY + ) + ) + ) + PluginSettings.addSettingsUpdateConsumer(clusterService) + Assertions.assertEquals(FilterByBackendRolesAccessStrategy.ALL.strategy, PluginSettings.getFilterByBackendAccessStrategy()) + } }