Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -48,6 +49,22 @@ internal object UserAccessManager : UserAccess {
return user.backendRoles
}

fun checkUserBackendRolesAccess(userBackendRoles: List<String>, objectAccess: List<String>): 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}
*/
Expand All @@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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<String> {
override fun validate(strategy: String) {
val allStrategies: List<String> = 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]"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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
*/
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -174,6 +192,14 @@ internal object PluginSettings {
Setting.Property.Final
)

val FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY: Setting<String> = 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<String?> = Setting
.simpleString(
Expand Down Expand Up @@ -214,6 +240,10 @@ internal object PluginSettings {
}
}

fun getFilterByBackendAccessStrategy(): String {
return filterByBackendRolesAccessStrategy
}

/**
* Returns list of additional settings available specific to this plugin.
*
Expand All @@ -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,
Expand All @@ -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)
}

/**
Expand All @@ -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
}
}

/**
Expand All @@ -278,12 +315,17 @@ 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
@OpenForTesting
fun reset() {
operationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS
defaultItemsQueryCount = DEFAULT_ITEMS_QUERY_COUNT_VALUE
filterByBackendRolesAccessStrategy = DEFAULT_FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,10 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() {
user: String,
password: String,
role: String,
backendRole: String,
backendRoles: Array<String>,
clusterPermissions: String?
) {
createUser(user, password, arrayOf(backendRole))
createUser(user, password, backendRoles)
createCustomRole(role, clusterPermissions)
createUserRolesMapping(role, arrayOf(user))
}
Expand Down
Loading
Loading