From 831dca30df9eb0dd28c6053641c424633141a90f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 16:16:19 -0400 Subject: [PATCH 001/122] Add a base setup for resource access evaluation Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 55 +++++++++++++++++-- .../resources/ResourceAccessEvaluator.java | 50 +++++++++++++++++ .../security/resources/package-info.java | 12 ++++ 3 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java create mode 100644 src/main/java/org/opensearch/security/resources/package-info.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 57ffc4df6f..f43e7a931b 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -68,6 +68,8 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; import org.opensearch.Version; +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.action.ActionRequest; import org.opensearch.action.search.PitService; import org.opensearch.action.search.SearchScrollAction; @@ -120,6 +122,7 @@ import org.opensearch.plugins.IdentityPlugin; import org.opensearch.plugins.MapperPlugin; import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; @@ -173,6 +176,7 @@ import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext; import org.opensearch.security.resolver.IndexResolverReplacer; +import org.opensearch.security.resources.ResourceAccessEvaluator; import org.opensearch.security.rest.DashboardsInfoAction; import org.opensearch.security.rest.SecurityConfigUpdateAction; import org.opensearch.security.rest.SecurityHealthAction; @@ -232,7 +236,8 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin MapperPlugin, // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings ExtensionAwarePlugin, - IdentityPlugin + IdentityPlugin, + ResourceAccessControlPlugin // CS-ENFORCE-SINGLE { @@ -268,6 +273,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile OpensearchDynamicSetting transportPassiveAuthSetting; private volatile PasswordHasher passwordHasher; private volatile DlsFlsBaseContext dlsFlsBaseContext; + private ResourceAccessEvaluator resourceAccessEvaluator; public static boolean isActionTraceEnabled() { @@ -481,6 +487,8 @@ public List run() { } } + + this.resourceAccessEvaluator = new ResourceAccessEvaluator(); } private void verifyTLSVersion(final String settings, final List configuredProtocols) { @@ -1367,7 +1375,7 @@ public List> getSettings() { settings.add(Setting.simpleString(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, Property.NodeScope, Property.Filtered)); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN + ".", Property.NodeScope)); // not filtered - // here + // here settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_OID, Property.NodeScope, Property.Filtered)); @@ -1383,8 +1391,8 @@ public List> getSettings() { );// not filtered here settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));// not - // filtered - // here + // filtered + // here settings.add( Setting.boolSetting( @@ -1428,8 +1436,8 @@ public List> getSettings() { Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered) ); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".", Property.NodeScope)); // not - // filtered - // here + // filtered + // here settings.add(Setting.simpleString(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, Property.NodeScope, Property.Filtered)); settings.add( @@ -2166,6 +2174,41 @@ private void tryAddSecurityProvider() { }); } + @Override + public Map> listAccessibleResources() { + return this.resourceAccessEvaluator.listAccessibleResources(); + } + + @Override + public List listAccessibleResourcesForPlugin(String systemIndexName) { + return this.resourceAccessEvaluator.listAccessibleResourcesForPlugin(systemIndexName); + } + + @Override + public boolean hasPermission(String resourceId, String systemIndexName) { + return this.resourceAccessEvaluator.hasPermission(resourceId, systemIndexName); + } + + @Override + public ResourceSharing shareWith(String resourceId, String systemIndexName, Map> entities) { + return this.resourceAccessEvaluator.shareWith(resourceId, systemIndexName, entities); + } + + @Override + public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> entities) { + return this.resourceAccessEvaluator.revokeAccess(resourceId, systemIndexName, entities); + } + + @Override + public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { + return this.resourceAccessEvaluator.deleteResourceSharingRecord(resourceId, systemIndexName); + } + + @Override + public boolean deleteAllResourceSharingRecordsFor(String entity) { + return this.resourceAccessEvaluator.deleteAllResourceSharingRecordsFor(entity); + } + public static class GuiceHolder implements LifecycleComponent { private static RepositoriesService repositoriesService; diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java b/src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java new file mode 100644 index 0000000000..3e4d73eb03 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java @@ -0,0 +1,50 @@ +/* + * 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.security.resources; + +import java.util.List; +import java.util.Map; + +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceSharing; + +public class ResourceAccessEvaluator { + + public Map> listAccessibleResources() { + return Map.of(); + } + + public List listAccessibleResourcesForPlugin(String s) { + return List.of(); + } + + public boolean hasPermission(String resourceId, String systemIndexName) { + return false; + } + + public ResourceSharing shareWith(String resourceId, String systemIndexName, Map> map) { + return null; + } + + public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> map) { + return null; + } + + public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { + return false; + } + + public boolean deleteAllResourceSharingRecordsFor(String entity) { + return false; + } + +} diff --git a/src/main/java/org/opensearch/security/resources/package-info.java b/src/main/java/org/opensearch/security/resources/package-info.java new file mode 100644 index 0000000000..855bdf81af --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/package-info.java @@ -0,0 +1,12 @@ +/* + * 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.security.resources; From 1c65eff05e2a9687df3333ec68251d29b439260b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 6 Sep 2024 13:17:35 -0400 Subject: [PATCH 002/122] Adds handler and other access management components for resource sharing Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 43 ++++-- .../resources/ResourceAccessEvaluator.java | 50 ------- .../resources/ResourceAccessHandler.java | 94 ++++++++++++ .../ResourceManagementRepository.java | 47 ++++++ .../ResourceSharingIndexHandler.java | 134 ++++++++++++++++++ .../ResourceSharingIndexListener.java | 82 +++++++++++ .../security/support/ConfigConstants.java | 3 + 7 files changed, 388 insertions(+), 65 deletions(-) delete mode 100644 src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java create mode 100644 src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java create mode 100644 src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java create mode 100644 src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java create mode 100644 src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index f43e7a931b..27e89f5c31 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -64,12 +64,10 @@ import org.apache.lucene.search.QueryCachingPolicy; import org.apache.lucene.search.Weight; -import org.opensearch.OpenSearchException; -import org.opensearch.OpenSearchSecurityException; -import org.opensearch.SpecialPermission; -import org.opensearch.Version; +import org.opensearch.*; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.ActionRequest; import org.opensearch.action.search.PitService; import org.opensearch.action.search.SearchScrollAction; @@ -176,7 +174,9 @@ import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext; import org.opensearch.security.resolver.IndexResolverReplacer; -import org.opensearch.security.resources.ResourceAccessEvaluator; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.resources.ResourceManagementRepository; +import org.opensearch.security.resources.ResourceSharingIndexListener; import org.opensearch.security.rest.DashboardsInfoAction; import org.opensearch.security.rest.SecurityConfigUpdateAction; import org.opensearch.security.rest.SecurityHealthAction; @@ -274,6 +274,9 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile PasswordHasher passwordHasher; private volatile DlsFlsBaseContext dlsFlsBaseContext; private ResourceAccessEvaluator resourceAccessEvaluator; + private ResourceManagementRepository rmr; + private ResourceAccessHandler resourceAccessHandler; + private final Set indicesToListen = new HashSet<>(); public static boolean isActionTraceEnabled() { @@ -488,7 +491,7 @@ public List run() { } - this.resourceAccessEvaluator = new ResourceAccessEvaluator(); + this.resourceAccessHandler = new ResourceAccessHandler(threadPool); } private void verifyTLSVersion(final String settings, final List configuredProtocols) { @@ -716,6 +719,12 @@ public void onIndexModule(IndexModule indexModule) { dlsFlsBaseContext ) ); + + if (this.indicesToListen.contains(indexModule.getIndex().getName())) { + indexModule.addIndexOperationListener(ResourceSharingIndexListener.getInstance()); + log.warn("Security plugin started listening to operations on index {}", indexModule.getIndex().getName()); + } + indexModule.forceQueryCacheProvider((indexSettings, nodeCache) -> new QueryCache() { @Override @@ -1199,6 +1208,8 @@ public Collection createComponents( e.subscribeForChanges(dcf); } + rmr = ResourceManagementRepository.create(settings, threadPool, localClient); + components.add(adminDns); components.add(cr); components.add(xffResolver); @@ -2073,6 +2084,8 @@ public void onNodeStarted(DiscoveryNode localNode) { if (!SSLConfig.isSslOnlyMode() && !client && !disabled && !useClusterStateToInitSecurityConfig(settings)) { cr.initOnNodeStart(); } + // create resource sharing index if absent + rmr.createResourceSharingIndexIfAbsent(); final Set securityModules = ReflectionHelper.getModulesLoaded(); log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); } @@ -2176,37 +2189,37 @@ private void tryAddSecurityProvider() { @Override public Map> listAccessibleResources() { - return this.resourceAccessEvaluator.listAccessibleResources(); + return this.resourceAccessHandler.listAccessibleResources(); } @Override public List listAccessibleResourcesForPlugin(String systemIndexName) { - return this.resourceAccessEvaluator.listAccessibleResourcesForPlugin(systemIndexName); + return this.resourceAccessHandler.listAccessibleResourcesForPlugin(systemIndexName); } @Override public boolean hasPermission(String resourceId, String systemIndexName) { - return this.resourceAccessEvaluator.hasPermission(resourceId, systemIndexName); + return this.resourceAccessHandler.hasPermission(resourceId, systemIndexName); } @Override - public ResourceSharing shareWith(String resourceId, String systemIndexName, Map> entities) { - return this.resourceAccessEvaluator.shareWith(resourceId, systemIndexName, entities); + public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { + return this.resourceAccessHandler.shareWith(resourceId, systemIndexName, shareWith); } @Override public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> entities) { - return this.resourceAccessEvaluator.revokeAccess(resourceId, systemIndexName, entities); + return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities); } @Override public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { - return this.resourceAccessEvaluator.deleteResourceSharingRecord(resourceId, systemIndexName); + return this.resourceAccessHandler.deleteResourceSharingRecord(resourceId, systemIndexName); } @Override - public boolean deleteAllResourceSharingRecordsFor(String entity) { - return this.resourceAccessEvaluator.deleteAllResourceSharingRecordsFor(entity); + public boolean deleteAllResourceSharingRecordsForCurrentUser() { + return this.resourceAccessHandler.deleteAllResourceSharingRecordsForCurrentUser(); } public static class GuiceHolder implements LifecycleComponent { diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java b/src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java deleted file mode 100644 index 3e4d73eb03..0000000000 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessEvaluator.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.security.resources; - -import java.util.List; -import java.util.Map; - -import org.opensearch.accesscontrol.resources.EntityType; -import org.opensearch.accesscontrol.resources.ResourceSharing; - -public class ResourceAccessEvaluator { - - public Map> listAccessibleResources() { - return Map.of(); - } - - public List listAccessibleResourcesForPlugin(String s) { - return List.of(); - } - - public boolean hasPermission(String resourceId, String systemIndexName) { - return false; - } - - public ResourceSharing shareWith(String resourceId, String systemIndexName, Map> map) { - return null; - } - - public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> map) { - return null; - } - - public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { - return false; - } - - public boolean deleteAllResourceSharingRecordsFor(String entity) { - return false; - } - -} diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java new file mode 100644 index 0000000000..0861854e13 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -0,0 +1,94 @@ +/* + * 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.security.resources; + +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.ThreadPool; + +public class ResourceAccessHandler { + private static final Logger LOGGER = LogManager.getLogger(ResourceAccessHandler.class); + + private final ThreadContext threadContext; + + public ResourceAccessHandler(final ThreadPool threadPool) { + super(); + this.threadContext = threadPool.getThreadContext(); + } + + public Map> listAccessibleResources() { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Listing accessible resource for: {}", user.getName()); + + // TODO add concrete implementation + return Map.of(); + } + + public List listAccessibleResourcesForPlugin(String systemIndex) { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Listing accessible resource within a system index {} for : {}", systemIndex, user.getName()); + + // TODO add concrete implementation + return List.of(); + } + + public boolean hasPermission(String resourceId, String systemIndexName) { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Checking if {} has permission to resource {}", user.getName(), resourceId); + + // TODO add concrete implementation + return false; + } + + public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith); + + // TODO add concrete implementation + return null; + } + + public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess); + + // TODO add concrete implementation + return null; + } + + public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, systemIndexName, user.getName()); + + // TODO add concrete implementation + return false; + } + + public boolean deleteAllResourceSharingRecordsForCurrentUser() { + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); + + // TODO add concrete implementation + return false; + } + +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java new file mode 100644 index 0000000000..df59516a41 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java @@ -0,0 +1,47 @@ +package org.opensearch.security.resources; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.threadpool.ThreadPool; + +public class ResourceManagementRepository { + + private static final Logger LOGGER = LogManager.getLogger(ConfigurationRepository.class); + + private final Client client; + + private final ThreadPool threadPool; + + private final ResourceSharingIndexHandler resourceSharingIndexHandler; + + protected ResourceManagementRepository( + final ThreadPool threadPool, + final Client client, + final ResourceSharingIndexHandler resourceSharingIndexHandler + ) { + this.client = client; + this.threadPool = threadPool; + this.resourceSharingIndexHandler = resourceSharingIndexHandler; + } + + public static ResourceManagementRepository create(Settings settings, final ThreadPool threadPool, Client client) { + final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; + return new ResourceManagementRepository( + threadPool, + client, + new ResourceSharingIndexHandler(resourceSharingIndex, settings, client, threadPool) + ); + } + + public void createResourceSharingIndexIfAbsent() { + // TODO check if this should be wrapped in an atomic completable future + + this.resourceSharingIndexHandler.createResourceSharingIndexIfAbsent(() -> null); + } + +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java new file mode 100644 index 0000000000..79ef85e7eb --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -0,0 +1,134 @@ +/* + * Copyright OpenSearch Contributors + * 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. + * + */ +package org.opensearch.security.resources; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.CreatedBy; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.create.CreateIndexResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.threadpool.ThreadPool; + +import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; + +public class ResourceSharingIndexHandler { + + private final static int MINIMUM_HASH_BITS = 128; + + private static final Logger LOGGER = LogManager.getLogger(ResourceSharingIndexHandler.class); + + private final Settings settings; + + private final Client client; + + private final String resourceSharingIndex; + + private final ThreadPool threadPool; + + public ResourceSharingIndexHandler(final String indexName, final Settings settings, final Client client, ThreadPool threadPool) { + this.resourceSharingIndex = indexName; + this.settings = settings; + this.client = client; + this.threadPool = threadPool; + } + + public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); + + public void createIndex(ActionListener listener) { + try (final ThreadContext.StoredContext threadContext = client.threadPool().getThreadContext().stashContext()) { + client.admin() + .indices() + .create( + new CreateIndexRequest(resourceSharingIndex).settings(INDEX_SETTINGS).waitForActiveShards(1), + ActionListener.runBefore(ActionListener.wrap(r -> { + if (r.isAcknowledged()) { + listener.onResponse(true); + } else listener.onFailure(new SecurityException("Couldn't create resource sharing index " + resourceSharingIndex)); + }, listener::onFailure), threadContext::restore) + ); + } + } + + // public void createIndexIfAbsent() { + // try { + // final Map indexSettings = ImmutableMap.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); + // final CreateIndexRequest createIndexRequest = new CreateIndexRequest(resourceSharingIndex).settings(indexSettings); + // final boolean ok = client.admin().indices().create(createIndexRequest).actionGet().isAcknowledged(); + // LOGGER.info("Resource sharing index {} created?: {}", resourceSharingIndex, ok); + // } catch (ResourceAlreadyExistsException resourceAlreadyExistsException) { + // LOGGER.info("Index {} already exists", resourceSharingIndex); + // } + // } + + public void createResourceSharingIndexIfAbsent(Callable callable) { + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { + CreateIndexRequest cir = new CreateIndexRequest(resourceSharingIndex); + ActionListener cirListener = ActionListener.wrap(response -> { + LOGGER.info("Resource sharing index {} created.", resourceSharingIndex); + callable.call(); + }, (failResponse) -> { + /* Index already exists, ignore and continue */ + LOGGER.info("Index {} already exists.", resourceSharingIndex); + try { + callable.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + this.client.admin().indices().create(cir, cirListener); + } + } + + public boolean indexResourceSharing( + String resourceId, + String resourceIndex, + CreatedBy createdBy, + ShareWith shareWith, + ActionListener listener + ) throws IOException { + createResourceSharingIndexIfAbsent(() -> { + ResourceSharing entry = new ResourceSharing(resourceIndex, resourceId, createdBy, shareWith); + + IndexRequest ir = client.prepareIndex(resourceSharingIndex) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setSource(entry.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .request(); + + LOGGER.info("Index Request: {}", ir.toString()); + + ActionListener irListener = ActionListener.wrap(idxResponse -> { + LOGGER.info("Created {} entry.", resourceSharingIndex); + listener.onResponse(idxResponse); + }, (failResponse) -> { + LOGGER.error(failResponse.getMessage()); + LOGGER.info("Failed to create {} entry.", resourceSharingIndex); + listener.onFailure(failResponse); + }); + client.index(ir, irListener); + return null; + }); + return true; + } +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java new file mode 100644 index 0000000000..7a2af9f3bd --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.client.Client; +import org.opensearch.core.index.shard.ShardId; +import org.opensearch.index.engine.Engine; +import org.opensearch.index.shard.IndexingOperationListener; +import org.opensearch.threadpool.ThreadPool; + +/** + * This class implements an index operation listener for operations performed on resources stored in plugin's indices + * These indices are defined on bootstrap and configured to listen in OpenSearchSecurityPlugin.java + */ +public class ResourceSharingIndexListener implements IndexingOperationListener { + + private final static Logger log = LogManager.getLogger(ResourceSharingIndexListener.class); + + private static final ResourceSharingIndexListener INSTANCE = new ResourceSharingIndexListener(); + + private boolean initialized; + + private ThreadPool threadPool; + + private Client client; + + private ResourceSharingIndexListener() {} + + public static ResourceSharingIndexListener getInstance() { + + return ResourceSharingIndexListener.INSTANCE; + + } + + public void initialize(ThreadPool threadPool, Client client) { + + if (initialized) { + return; + } + + initialized = true; + + this.threadPool = threadPool; + + this.client = client; + + } + + public boolean isInitialized() { + return initialized; + } + + @Override + + public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult result) { + + // implement a check to see if a resource was updated + log.warn("postIndex called on " + shardId.getIndexName()); + + String resourceId = index.id(); + + String resourceIndex = shardId.getIndexName(); + } + + @Override + + public void postDelete(ShardId shardId, Engine.Delete delete, Engine.DeleteResult result) { + + // implement a check to see if a resource was deleted + log.warn("postDelete called on " + shardId.getIndexName()); + } + +} diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index f35afc6489..456e9586ca 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -370,6 +370,9 @@ public enum RolesMappingResolution { // Variable for initial admin password support public static final String OPENSEARCH_INITIAL_ADMIN_PASSWORD = "OPENSEARCH_INITIAL_ADMIN_PASSWORD"; + // Resource sharing index + public static final String OPENSEARCH_RESOURCE_SHARING_INDEX = ".opensearch_resource_sharing"; + public static Set getSettingAsSet( final Settings settings, final String key, From 118cb07b9ff45a6722e3d5e4d222106c6da66de6 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 6 Sep 2024 13:18:12 -0400 Subject: [PATCH 003/122] Adds sample resource plugin Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 167 ++++++++++++++++++ .../opensearch/security/sample/Resource.java | 8 + .../security/sample/SampleResourcePlugin.java | 165 +++++++++++++++++ .../create/CreateSampleResourceAction.java | 30 ++++ .../create/CreateSampleResourceRequest.java | 55 ++++++ .../create/CreateSampleResourceResponse.java | 55 ++++++ .../CreateSampleResourceRestAction.java | 56 ++++++ .../CreateSampleResourceTransportAction.java | 32 ++++ .../sample/actions/create/SampleResource.java | 45 +++++ .../list/ListSampleResourceAction.java | 29 +++ .../list/ListSampleResourceRequest.java | 39 ++++ .../list/ListSampleResourceResponse.java | 55 ++++++ .../list/ListSampleResourceRestAction.java | 44 +++++ .../ListSampleResourceTransportAction.java | 52 ++++++ .../transport/CreateResourceRequest.java | 50 ++++++ .../transport/CreateResourceResponse.java | 55 ++++++ .../CreateResourceTransportAction.java | 103 +++++++++++ .../plugin-metadata/plugin-security.policy | 3 + .../test/resources/security/esnode-key.pem | 28 +++ .../src/test/resources/security/esnode.pem | 25 +++ .../src/test/resources/security/kirk-key.pem | 28 +++ .../src/test/resources/security/kirk.pem | 27 +++ .../src/test/resources/security/root-ca.pem | 28 +++ .../src/test/resources/security/sample.pem | 25 +++ .../src/test/resources/security/test-kirk.jks | Bin 0 -> 3766 bytes settings.gradle | 3 + 26 files changed, 1207 insertions(+) create mode 100644 sample-resource-plugin/build.gradle create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy create mode 100644 sample-resource-plugin/src/test/resources/security/esnode-key.pem create mode 100644 sample-resource-plugin/src/test/resources/security/esnode.pem create mode 100644 sample-resource-plugin/src/test/resources/security/kirk-key.pem create mode 100644 sample-resource-plugin/src/test/resources/security/kirk.pem create mode 100644 sample-resource-plugin/src/test/resources/security/root-ca.pem create mode 100644 sample-resource-plugin/src/test/resources/security/sample.pem create mode 100644 sample-resource-plugin/src/test/resources/security/test-kirk.jks diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle new file mode 100644 index 0000000000..6d4b084580 --- /dev/null +++ b/sample-resource-plugin/build.gradle @@ -0,0 +1,167 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'opensearch.opensearchplugin' +apply plugin: 'opensearch.testclusters' +apply plugin: 'opensearch.java-rest-test' + +import org.opensearch.gradle.test.RestIntegTestTask + + +opensearchplugin { + name 'opensearch-security-sample-resource-plugin' + description 'Sample plugin that extends OpenSearch Resource Plugin' + classname 'org.opensearch.security.sampleresourceplugin.SampleResourcePlugin' + extendedPlugins = ['opensearch-security'] +} + +ext { + projectSubstitutions = [:] + licenseFile = rootProject.file('LICENSE.txt') + noticeFile = rootProject.file('NOTICE.txt') +} + +repositories { + mavenLocal() + mavenCentral() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } +} + +dependencies { +} + +def es_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile +es_tmp_dir.mkdirs() + +File repo = file("$buildDir/testclusters/repo") +def _numNodes = findProperty('numNodes') as Integer ?: 1 + +licenseHeaders.enabled = true +validateNebulaPom.enabled = false +testingConventions.enabled = false +loggerUsageCheck.enabled = false + +javaRestTest.dependsOn(rootProject.assemble) +javaRestTest { + systemProperty 'tests.security.manager', 'false' +} +testClusters.javaRestTest { + testDistribution = 'INTEG_TEST' +} + +task integTest(type: RestIntegTestTask) { + description = "Run tests against a cluster" + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath +} +tasks.named("check").configure { dependsOn(integTest) } + +integTest { + if (project.hasProperty('excludeTests')) { + project.properties['excludeTests']?.replaceAll('\\s', '')?.split('[,;]')?.each { + exclude "${it}" + } + } + systemProperty 'tests.security.manager', 'false' + systemProperty 'java.io.tmpdir', es_tmp_dir.absolutePath + + systemProperty "https", System.getProperty("https") + systemProperty "user", System.getProperty("user") + systemProperty "password", System.getProperty("password") + // Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for + // requests. The 'doFirst' delays reading the debug setting on the cluster till execution time. + doFirst { + // Tell the test JVM if the cluster JVM is running under a debugger so that tests can + // use longer timeouts for requests. + def isDebuggingCluster = getDebug() || System.getProperty("test.debug") != null + systemProperty 'cluster.debug', isDebuggingCluster + // Set number of nodes system property to be used in tests + systemProperty 'cluster.number_of_nodes', "${_numNodes}" + // There seems to be an issue when running multi node run or integ tasks with unicast_hosts + // not being written, the waitForAllConditions ensures it's written + getClusters().forEach { cluster -> + cluster.waitForAllConditions() + } + } + + // The -Dcluster.debug option makes the cluster debuggable; this makes the tests debuggable + if (System.getProperty("test.debug") != null) { + jvmArgs '-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=8000' + } + if (System.getProperty("tests.rest.bwcsuite") == null) { + filter { + excludeTestsMatching "org.opensearch.security.sampleextension.bwc.*IT" + } + } +} +project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build')) +Zip bundle = (Zip) project.getTasks().getByName("bundlePlugin"); +Zip rootBundle = (Zip) rootProject.getTasks().getByName("bundlePlugin"); +integTest.dependsOn(bundle) +integTest.getClusters().forEach{c -> { + c.plugin(rootProject.getObjects().fileProperty().value(rootBundle.getArchiveFile())) + c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile())) +}} + +testClusters.integTest { + testDistribution = 'INTEG_TEST' + + // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1 + if (_numNodes > 1) numberOfNodes = _numNodes + // When running integration tests it doesn't forward the --debug-jvm to the cluster anymore + // i.e. we have to use a custom property to flag when we want to debug OpenSearch JVM + // since we also support multi node integration tests we increase debugPort per node + if (System.getProperty("cluster.debug") != null) { + def debugPort = 5005 + nodes.forEach { node -> + node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=*:${debugPort}") + debugPort += 1 + } + } + setting 'path.repo', repo.absolutePath +} + +afterEvaluate { + testClusters.integTest.nodes.each { node -> + def plugins = node.plugins + def firstPlugin = plugins.get(0) + if (firstPlugin.provider == project.bundlePlugin.archiveFile) { + plugins.remove(0) + plugins.add(firstPlugin) + } + + node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem")) + node.extraConfigFile("kirk-key.pem", file("src/test/resources/security/kirk-key.pem")) + node.extraConfigFile("esnode.pem", file("src/test/resources/security/esnode.pem")) + node.extraConfigFile("esnode-key.pem", file("src/test/resources/security/esnode-key.pem")) + node.extraConfigFile("root-ca.pem", file("src/test/resources/security/root-ca.pem")) + node.setting("plugins.security.ssl.transport.pemcert_filepath", "esnode.pem") + node.setting("plugins.security.ssl.transport.pemkey_filepath", "esnode-key.pem") + node.setting("plugins.security.ssl.transport.pemtrustedcas_filepath", "root-ca.pem") + node.setting("plugins.security.ssl.transport.enforce_hostname_verification", "false") + node.setting("plugins.security.ssl.http.enabled", "true") + node.setting("plugins.security.ssl.http.pemcert_filepath", "esnode.pem") + node.setting("plugins.security.ssl.http.pemkey_filepath", "esnode-key.pem") + node.setting("plugins.security.ssl.http.pemtrustedcas_filepath", "root-ca.pem") + node.setting("plugins.security.allow_unsafe_democertificates", "true") + node.setting("plugins.security.allow_default_init_securityindex", "true") + node.setting("plugins.security.authcz.admin_dn", "\n - CN=kirk,OU=client,O=client,L=test,C=de") + node.setting("plugins.security.audit.type", "internal_opensearch") + node.setting("plugins.security.enable_snapshot_restore_privilege", "true") + node.setting("plugins.security.check_snapshot_restore_write_privileges", "true") + node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]") + } +} + +run { + doFirst { + // There seems to be an issue when running multi node run or integ tasks with unicast_hosts + // not being written, the waitForAllConditions ensures it's written + getClusters().forEach { cluster -> + cluster.waitForAllConditions() + } + } + useCluster testClusters.integTest +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java new file mode 100644 index 0000000000..6126fdb092 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java @@ -0,0 +1,8 @@ +package org.opensearch.security.sample; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.xcontent.ToXContentFragment; + +public abstract class Resource implements NamedWriteable, ToXContentFragment { + protected abstract String getResourceIndex(); +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java new file mode 100644 index 0000000000..58e4daa95c --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java @@ -0,0 +1,165 @@ +/* + * Copyright OpenSearch Contributors + * 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. + */ +package org.opensearch.security.sample; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.action.ActionRequest; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.lifecycle.Lifecycle; +import org.opensearch.common.lifecycle.LifecycleComponent; +import org.opensearch.common.lifecycle.LifecycleListener; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourcePlugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import org.opensearch.script.ScriptService; +import org.opensearch.security.sample.actions.create.CreateSampleResourceAction; +import org.opensearch.security.sample.actions.create.CreateSampleResourceRestAction; +import org.opensearch.security.sample.actions.create.CreateSampleResourceTransportAction; +import org.opensearch.security.sample.actions.list.ListSampleResourceAction; +import org.opensearch.security.sample.actions.list.ListSampleResourceRestAction; +import org.opensearch.security.sample.actions.list.ListSampleResourceTransportAction; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +/** + * Sample Resource plugin. + * It uses ".sample_resources" index to manage its resources, and exposes a REST API + * + */ +public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { + private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); + + public static final String RESOURCE_INDEX_NAME = ".sample_resources"; + + private Client client; + + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + this.client = client; + return Collections.emptyList(); + } + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return List.of(new CreateSampleResourceRestAction(), new ListSampleResourceRestAction()); + } + + @Override + public List> getActions() { + return List.of( + new ActionHandler<>(CreateSampleResourceAction.INSTANCE, CreateSampleResourceTransportAction.class), + new ActionHandler<>(ListSampleResourceAction.INSTANCE, ListSampleResourceTransportAction.class) + ); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Example index with resources"); + return Collections.singletonList(systemIndexDescriptor); + } + + @Override + public String getResourceType() { + return ""; + } + + @Override + public String getResourceIndex() { + return ""; + } + + @Override + public Collection> getGuiceServiceClasses() { + final List> services = new ArrayList<>(1); + services.add(GuiceHolder.class); + return services; + } + + public static class GuiceHolder implements LifecycleComponent { + + private static ResourceService resourceService; + + @Inject + public GuiceHolder(final ResourceService resourceService) { + GuiceHolder.resourceService = resourceService; + } + + public static ResourceService getResourceService() { + return resourceService; + } + + @Override + public void close() {} + + @Override + public Lifecycle.State lifecycleState() { + return null; + } + + @Override + public void addLifecycleListener(LifecycleListener listener) {} + + @Override + public void removeLifecycleListener(LifecycleListener listener) {} + + @Override + public void start() {} + + @Override + public void stop() {} + + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java new file mode 100644 index 0000000000..1e106d1a47 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.create; + +import org.opensearch.action.ActionType; +import org.opensearch.security.sample.transport.CreateResourceResponse; + +/** + * Action to create a sample resource + */ +public class CreateSampleResourceAction extends ActionType { + /** + * Create sample resource action instance + */ + public static final CreateSampleResourceAction INSTANCE = new CreateSampleResourceAction(); + /** + * Create sample resource action name + */ + public static final String NAME = "cluster:admin/sampleresource/create"; + + private CreateSampleResourceAction() { + super(NAME, CreateResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java new file mode 100644 index 0000000000..35815f9a17 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.create; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.security.sample.Resource; + +/** + * Request object for CreateSampleResource transport action + */ +public class CreateSampleResourceRequest extends ActionRequest { + + private final Resource resource; + + /** + * Default constructor + */ + public CreateSampleResourceRequest(Resource resource) { + this.resource = resource; + } + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public CreateSampleResourceRequest(final StreamInput in) throws IOException { + this.resource = new SampleResource(in); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + resource.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public Resource getResource() { + return this.resource; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java new file mode 100644 index 0000000000..476d63d5fe --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.create; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +/** + * Response to a CreateSampleResourceRequest + */ +public class CreateSampleResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public CreateSampleResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public CreateSampleResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java new file mode 100644 index 0000000000..00e41bbdf9 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.create; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.security.sample.transport.CreateResourceRequest; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.POST; + +public class CreateSampleResourceRestAction extends BaseRestHandler { + + public CreateSampleResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(POST, "/_plugins/resource_sharing_example/resource")); + } + + @Override + public String getName() { + return "create_sample_resource"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String name = (String) source.get("name"); + SampleResource resource = new SampleResource(); + resource.setName(name); + final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest<>(resource); + return channel -> client.executeLocally( + CreateSampleResourceAction.INSTANCE, + createSampleResourceRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java new file mode 100644 index 0000000000..23c84aec82 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.create; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.security.sample.transport.CreateResourceTransportAction; +import org.opensearch.transport.TransportService; + +import static org.opensearch.security.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for CreateSampleResource. + */ +public class CreateSampleResourceTransportAction extends CreateResourceTransportAction { + private static final Logger log = LogManager.getLogger(CreateSampleResourceTransportAction.class); + + @Inject + public CreateSampleResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(transportService, actionFilters, nodeClient, CreateSampleResourceAction.NAME, RESOURCE_INDEX_NAME, SampleResource::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java new file mode 100644 index 0000000000..6bc91c369a --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java @@ -0,0 +1,45 @@ +package org.opensearch.security.sample.actions.create; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.security.sample.Resource; + +import static org.opensearch.security.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +public class SampleResource extends Resource { + + private String name; + + public SampleResource() {} + + SampleResource(StreamInput in) throws IOException { + this.name = in.readString(); + } + + @Override + public String getResourceIndex() { + return RESOURCE_INDEX_NAME; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("name", name).endObject(); + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + streamOutput.writeString(name); + } + + @Override + public String getWriteableName() { + return "sampled_resource"; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java new file mode 100644 index 0000000000..89bee6c093 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.list; + +import org.opensearch.action.ActionType; + +/** + * Action to list sample resources + */ +public class ListSampleResourceAction extends ActionType { + /** + * List sample resource action instance + */ + public static final ListSampleResourceAction INSTANCE = new ListSampleResourceAction(); + /** + * List sample resource action name + */ + public static final String NAME = "cluster:admin/sampleresource/list"; + + private ListSampleResourceAction() { + super(NAME, ListSampleResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java new file mode 100644 index 0000000000..27d1cd6cfd --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.list; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +/** + * Request object for ListSampleResource transport action + */ +public class ListSampleResourceRequest extends ActionRequest { + + public ListSampleResourceRequest() {} + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public ListSampleResourceRequest(final StreamInput in) throws IOException {} + + @Override + public void writeTo(final StreamOutput out) throws IOException {} + + @Override + public ActionRequestValidationException validate() { + return null; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java new file mode 100644 index 0000000000..021d456cab --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.list; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +/** + * Response to a ListSampleResourceRequest + */ +public class ListSampleResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public ListSampleResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public ListSampleResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java new file mode 100644 index 0000000000..e56fd08179 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.list; + +import java.util.List; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class ListSampleResourceRestAction extends BaseRestHandler { + + public ListSampleResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(GET, "/_plugins/resource_sharing_example/resource")); + } + + @Override + public String getName() { + return "list_sample_resources"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + final ListSampleResourceRequest listSampleResourceRequest = new ListSampleResourceRequest(); + return channel -> client.executeLocally( + ListSampleResourceAction.INSTANCE, + listSampleResourceRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java new file mode 100644 index 0000000000..e04435725e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.actions.list; + +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.index.query.MatchAllQueryBuilder; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +/** + * Transport action for ListSampleResource. + */ +public class ListSampleResourceTransportAction extends HandledTransportAction { + private final TransportService transportService; + private final Client nodeClient; + + @Inject + public ListSampleResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(ListSampleResourceAction.NAME, transportService, actionFilters, ListSampleResourceRequest::new); + this.transportService = transportService; + this.nodeClient = nodeClient; + } + + @Override + protected void doExecute(Task task, ListSampleResourceRequest request, ActionListener listener) { + try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + SearchRequest sr = new SearchRequest(".resource-sharing"); + SearchSourceBuilder matchAllQuery = new SearchSourceBuilder(); + matchAllQuery.query(new MatchAllQueryBuilder()); + sr.source(matchAllQuery); + /* Index already exists, ignore and continue */ + ActionListener searchListener = ActionListener.wrap(response -> { + listener.onResponse(new ListSampleResourceResponse(response.toString())); + }, listener::onFailure); + nodeClient.search(sr, searchListener); + } + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java new file mode 100644 index 0000000000..ea1eb57755 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.transport; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.security.sample.Resource; + +/** + * Request object for CreateSampleResource transport action + */ +public class CreateResourceRequest extends ActionRequest { + + private final T resource; + + /** + * Default constructor + */ + public CreateResourceRequest(T resource) { + this.resource = resource; + } + + public CreateResourceRequest(StreamInput in, Reader resourceReader) throws IOException { + this.resource = resourceReader.read(in); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + resource.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public Resource getResource() { + return this.resource; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java new file mode 100644 index 0000000000..892cd74108 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.transport; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +/** + * Response to a CreateSampleResourceRequest + */ +public class CreateResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public CreateResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public CreateResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java new file mode 100644 index 0000000000..f95e2d5d5a --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java @@ -0,0 +1,103 @@ +/* + * 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. + */ + +package org.opensearch.security.sample.transport; + +import java.io.IOException; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.admin.indices.create.CreateIndexResponse; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.security.sample.Resource; +import org.opensearch.security.sample.SampleResourcePlugin; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; + +/** + * Transport action for CreateSampleResource. + */ +public class CreateResourceTransportAction extends HandledTransportAction< + CreateResourceRequest, + CreateResourceResponse> { + private static final Logger log = LogManager.getLogger(CreateResourceTransportAction.class); + + private final TransportService transportService; + private final Client nodeClient; + private final String resourceIndex; + + public CreateResourceTransportAction( + TransportService transportService, + ActionFilters actionFilters, + Client nodeClient, + String actionName, + String resourceIndex, + Writeable.Reader resourceReader + ) { + super(actionName, transportService, actionFilters, (in) -> new CreateResourceRequest(in, resourceReader)); + this.transportService = transportService; + this.nodeClient = nodeClient; + this.resourceIndex = resourceIndex; + } + + @Override + protected void doExecute(Task task, CreateResourceRequest request, ActionListener listener) { + try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + CreateIndexRequest cir = new CreateIndexRequest(resourceIndex); + ActionListener cirListener = ActionListener.wrap( + response -> { createResource(request, listener); }, + (failResponse) -> { + /* Index already exists, ignore and continue */ + createResource(request, listener); + } + ); + nodeClient.admin().indices().create(cir, cirListener); + } + } + + private void createResource(CreateResourceRequest request, ActionListener listener) { + Resource sample = request.getResource(); + try { + IndexRequest ir = nodeClient.prepareIndex(resourceIndex) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setSource(sample.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .request(); + + log.warn("Index Request: {}", ir.toString()); + + ActionListener irListener = ActionListener.wrap(idxResponse -> { + log.info("Created resource: {}", idxResponse.toString()); + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + ResourceSharing sharing = rs.getResourceAccessControlPlugin() + .shareWith(idxResponse.getId(), idxResponse.getIndex(), Map.of()); + log.info("Created resource sharing entry: {}", sharing.toString()); + }, listener::onFailure); + nodeClient.index(ir, irListener); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + // TODO add delete implementation as a separate transport action +} diff --git a/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy b/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy new file mode 100644 index 0000000000..a5dfc33a87 --- /dev/null +++ b/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy @@ -0,0 +1,3 @@ +grant { + permission java.lang.RuntimePermission "getClassLoader"; +}; \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/esnode-key.pem b/sample-resource-plugin/src/test/resources/security/esnode-key.pem new file mode 100644 index 0000000000..e90562be43 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/esnode-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCm93kXteDQHMAv +bUPNPW5pyRHKDD42XGWSgq0k1D29C/UdyL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0 +o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0HGkn47XVu3EwbfrTENg3jFu+Oem6a/50 +1SzITzJWtS0cn2dIFOBimTVpT/4Zv5qrXA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1 +MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8ndibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b +6l+KLo3IKpfTbAIJXIO+M67FLtWKtttDao94B069skzKk6FPgW/OZh6PRCD0oxOa +vV+ld2SjAgMBAAECggEAQK1+uAOZeaSZggW2jQut+MaN4JHLi61RH2cFgU3COLgo +FIiNjFn8f2KKU3gpkt1It8PjlmprpYut4wHI7r6UQfuv7ZrmncRiPWHm9PB82+ZQ +5MXYqj4YUxoQJ62Cyz4sM6BobZDrjG6HHGTzuwiKvHHkbsEE9jQ4E5m7yfbVvM0O +zvwrSOM1tkZihKSTpR0j2+taji914tjBssbn12TMZQL5ItGnhR3luY8mEwT9MNkZ +xg0VcREoAH+pu9FE0vPUgLVzhJ3be7qZTTSRqv08bmW+y1plu80GbppePcgYhEow +dlW4l6XPJaHVSn1lSFHE6QAx6sqiAnBz0NoTPIaLyQKBgQDZqDOlhCRciMRicSXn +7yid9rhEmdMkySJHTVFOidFWwlBcp0fGxxn8UNSBcXdSy7GLlUtH41W9PWl8tp9U +hQiiXORxOJ7ZcB80uNKXF01hpPj2DpFPWyHFxpDkWiTAYpZl68rOlYujxZUjJIej +VvcykBC2BlEOG9uZv2kxcqLyJwKBgQDEYULTxaTuLIa17wU3nAhaainKB3vHxw9B +Ksy5p3ND43UNEKkQm7K/WENx0q47TA1mKD9i+BhaLod98mu0YZ+BCUNgWKcBHK8c +uXpauvM/pLhFLXZ2jvEJVpFY3J79FSRK8bwE9RgKfVKMMgEk4zOyZowS8WScOqiy +hnQn1vKTJQKBgElhYuAnl9a2qXcC7KOwRsJS3rcKIVxijzL4xzOyVShp5IwIPbOv +hnxBiBOH/JGmaNpFYBcBdvORE9JfA4KMQ2fx53agfzWRjoPI1/7mdUk5RFI4gRb/ +A3jZRBoopgFSe6ArCbnyQxzYzToG48/Wzwp19ZxYrtUR4UyJct6f5n27AoGBAJDh +KIpQQDOvCdtjcbfrF4aM2DPCfaGPzENJriwxy6oEPzDaX8Bu/dqI5Ykt43i/zQrX +GpyLaHvv4+oZVTiI5UIvcVO9U8hQPyiz9f7F+fu0LHZs6f7hyhYXlbe3XFxeop3f +5dTKdWgXuTTRF2L9dABkA2deS9mutRKwezWBMQk5AoGBALPtX0FrT1zIosibmlud +tu49A/0KZu4PBjrFMYTSEWGNJez3Fb2VsJwylVl6HivwbP61FhlYfyksCzQQFU71 ++x7Nmybp7PmpEBECr3deoZKQ/acNHn0iwb0It+YqV5+TquQebqgwK6WCLsMuiYKT +bg/ch9Rhxbq22yrVgWHh6epp +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/esnode.pem b/sample-resource-plugin/src/test/resources/security/esnode.pem new file mode 100644 index 0000000000..44101f0b37 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/esnode.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl +MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud +yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 +HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr +XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n +dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD +ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R +BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA +AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo +wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ +KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz +pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi +7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh +hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L +camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg +PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/kirk-key.pem b/sample-resource-plugin/src/test/resources/security/kirk-key.pem new file mode 100644 index 0000000000..1949c26139 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/kirk-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCVXDgEJQorgfXp +gpY0TgF55bD2xuzxN5Dc9rDfgWxrsOvOloMpd7k6FR71bKWjJi1KptSmM/cDElky +AWYKSfYWGiGxsQ+EQW+6kwCfEOHXQldn+0+JcWqP+osSPjtJfwRvRN5kRqP69MPo +7U0N2kdqenqMWjmG1chDGLRSOEGU5HIBiDxsZtOcvMaJ8b1eaW0lvS+6gFQ80AvB +GBkDDCOHHLtDXBylrZk2CQP8AzxNicIZ4B8G3CG3OHA8+nBtEtxZoIihrrkqlMt+ +b/5N8u8zB0Encew0kdrc4R/2wS//ahr6U+9Siq8T7WsUtGwKj3BJClg6OyDJRhlu +y2gFnxoPAgMBAAECggEAP5TOycDkx+megAWVoHV2fmgvgZXkBrlzQwUG/VZQi7V4 +ZGzBMBVltdqI38wc5MtbK3TCgHANnnKgor9iq02Z4wXDwytPIiti/ycV9CDRKvv0 +TnD2hllQFjN/IUh5n4thHWbRTxmdM7cfcNgX3aZGkYbLBVVhOMtn4VwyYu/Mxy8j +xClZT2xKOHkxqwmWPmdDTbAeZIbSv7RkIGfrKuQyUGUaWhrPslvYzFkYZ0umaDgQ +OAthZew5Bz3OfUGOMPLH61SVPuJZh9zN1hTWOvT65WFWfsPd2yStI+WD/5PU1Doo +1RyeHJO7s3ug8JPbtNJmaJwHe9nXBb/HXFdqb976yQKBgQDNYhpu+MYSYupaYqjs +9YFmHQNKpNZqgZ4ceRFZ6cMJoqpI5dpEMqToFH7tpor72Lturct2U9nc2WR0HeEs +/6tiptyMPTFEiMFb1opQlXF2ae7LeJllntDGN0Q6vxKnQV+7VMcXA0Y8F7tvGDy3 +qJu5lfvB1mNM2I6y/eMxjBuQhwKBgQC6K41DXMFro0UnoO879pOQYMydCErJRmjG +/tZSy3Wj4KA/QJsDSViwGfvdPuHZRaG9WtxdL6kn0w1exM9Rb0bBKl36lvi7o7xv +M+Lw9eyXMkww8/F5d7YYH77gIhGo+RITkKI3+5BxeBaUnrGvmHrpmpgRXWmINqr0 +0jsnN3u0OQKBgCf45vIgItSjQb8zonLz2SpZjTFy4XQ7I92gxnq8X0Q5z3B+o7tQ +K/4rNwTju/sGFHyXAJlX+nfcK4vZ4OBUJjP+C8CTjEotX4yTNbo3S6zjMyGQqDI5 +9aIOUY4pb+TzeUFJX7If5gR+DfGyQubvvtcg1K3GHu9u2l8FwLj87sRzAoGAflQF +RHuRiG+/AngTPnZAhc0Zq0kwLkpH2Rid6IrFZhGLy8AUL/O6aa0IGoaMDLpSWUJp +nBY2S57MSM11/MVslrEgGmYNnI4r1K25xlaqV6K6ztEJv6n69327MS4NG8L/gCU5 +3pEm38hkUi8pVYU7in7rx4TCkrq94OkzWJYurAkCgYATQCL/rJLQAlJIGulp8s6h +mQGwy8vIqMjAdHGLrCS35sVYBXG13knS52LJHvbVee39AbD5/LlWvjJGlQMzCLrw +F7oILW5kXxhb8S73GWcuMbuQMFVHFONbZAZgn+C9FW4l7XyRdkrbR1MRZ2km8YMs +/AHmo368d4PSNRMMzLHw8Q== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/kirk.pem b/sample-resource-plugin/src/test/resources/security/kirk.pem new file mode 100644 index 0000000000..36b7e19a75 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/kirk.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEmDCCA4CgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLcwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzA0MjRaFw0zNDAyMTcxNzA0MjRaME0xCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ8wDQYDVQQKDAZjbGllbnQxDzANBgNVBAsMBmNs +aWVudDENMAsGA1UEAwwEa2lyazCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAJVcOAQlCiuB9emCljROAXnlsPbG7PE3kNz2sN+BbGuw686Wgyl3uToVHvVs +paMmLUqm1KYz9wMSWTIBZgpJ9hYaIbGxD4RBb7qTAJ8Q4ddCV2f7T4lxao/6ixI+ +O0l/BG9E3mRGo/r0w+jtTQ3aR2p6eoxaOYbVyEMYtFI4QZTkcgGIPGxm05y8xonx +vV5pbSW9L7qAVDzQC8EYGQMMI4ccu0NcHKWtmTYJA/wDPE2JwhngHwbcIbc4cDz6 +cG0S3FmgiKGuuSqUy35v/k3y7zMHQSdx7DSR2tzhH/bBL/9qGvpT71KKrxPtaxS0 +bAqPcEkKWDo7IMlGGW7LaAWfGg8CAwEAAaOCASswggEnMAwGA1UdEwEB/wQCMAAw +DgYDVR0PAQH/BAQDAgXgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMB0GA1UdDgQW +BBSjMS8tgguX/V7KSGLoGg7K6XMzIDCBzwYDVR0jBIHHMIHEgBQXh9+gWutmEqfV +0Pi6EkU8tysAnKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS +JomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAf +BgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBs +ZSBDb20gSW5jLiBSb290IENBghQNZAmZZn3EFOxBR4630XlhI+mo4jANBgkqhkiG +9w0BAQsFAAOCAQEACEUPPE66/Ot3vZqRGpjDjPHAdtOq+ebaglQhvYcnDw8LOZm8 +Gbh9M88CiO6UxC8ipQLTPh2yyeWArkpJzJK/Pi1eoF1XLiAa0sQ/RaJfQWPm9dvl +1ZQeK5vfD4147b3iBobwEV+CR04SKow0YeEEzAJvzr8YdKI6jqr+2GjjVqzxvRBy +KRVHWCFiR7bZhHGLq3br8hSu0hwjb3oGa1ZI8dui6ujyZt6nm6BoEkau3G/6+zq9 +E6vX3+8Fj4HKCAL6i0SwfGmEpTNp5WUhqibK/fMhhmMT4Mx6MxkT+OFnIjdUU0S/ +e3kgnG8qjficUr38CyEli1U0M7koIXUZI7r+LQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/root-ca.pem b/sample-resource-plugin/src/test/resources/security/root-ca.pem new file mode 100644 index 0000000000..d33f5f7216 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/root-ca.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIExjCCA66gAwIBAgIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzAwMzZaFw0zNDAyMTcxNzAwMzZaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEPyN7J9VGPyJcQmCBl5TGwfSzvVdWwoQU +j9aEsdfFJ6pBCDQSsj8Lv4RqL0dZra7h7SpZLLX/YZcnjikrYC+rP5OwsI9xEE/4 +U98CsTBPhIMgqFK6SzNE5494BsAk4cL72dOOc8tX19oDS/PvBULbNkthQ0aAF1dg +vbrHvu7hq7LisB5ZRGHVE1k/AbCs2PaaKkn2jCw/b+U0Ml9qPuuEgz2mAqJDGYoA +WSR4YXrOcrmPuRqbws464YZbJW898/0Pn/U300ed+4YHiNYLLJp51AMkR4YEw969 +VRPbWIvLrd0PQBooC/eLrL6rvud/GpYhdQEUx8qcNCKd4bz3OaQ5AgMBAAGjggEW +MIIBEjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQU +F4ffoFrrZhKn1dD4uhJFPLcrAJwwgc8GA1UdIwSBxzCBxIAUF4ffoFrrZhKn1dD4 +uhJFPLcrAJyhgZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJ +k/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYD +VQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUg +Q29tIEluYy4gUm9vdCBDQYIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcN +AQELBQADggEBAL3Q3AHUhMiLUy6OlLSt8wX9I2oNGDKbBu0atpUNDztk/0s3YLQC +YuXgN4KrIcMXQIuAXCx407c+pIlT/T1FNn+VQXwi56PYzxQKtlpoKUL3oPQE1d0V +6EoiNk+6UodvyZqpdQu7fXVentRMk1QX7D9otmiiNuX+GSxJhJC2Lyzw65O9EUgG +1yVJon6RkUGtqBqKIuLksKwEr//ELnjmXit4LQKSnqKr0FTCB7seIrKJNyb35Qnq +qy9a/Unhokrmdda1tr6MbqU8l7HmxLuSd/Ky+L0eDNtYv6YfMewtjg0TtAnFyQov +rdXmeq1dy9HLo3Ds4AFz3Gx9076TxcRS/iI= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/sample.pem b/sample-resource-plugin/src/test/resources/security/sample.pem new file mode 100644 index 0000000000..44101f0b37 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/sample.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl +MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud +yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 +HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr +XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n +dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD +ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R +BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA +AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo +wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ +KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz +pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi +7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh +hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L +camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg +PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/test-kirk.jks b/sample-resource-plugin/src/test/resources/security/test-kirk.jks new file mode 100644 index 0000000000000000000000000000000000000000..6c8c5ef77e20980f8c78295b159256b805da6a28 GIT binary patch literal 3766 zcmd^=c{r47AIImJ%`(PV###wuU&o%k$xbMgr4m`Pk2Tv-j4?=zEwY?!X|aVw)I`=A zPAY52Rt6yODkPjhAQ%WsfbL*f;mp!-018Nf*#Q6sf)b!}Nv;s_8gzOC@mTmi+D9F}jyYkhL=#Xk3eYM2csmxKA&W!xAdE{tZ2mEGS z;L%QU`DHcrbdbw$3GsKUvmfQu0Z^?sH7B)!W)eLbG*fXB^G$&6CbCnj4~ z*J>Rkut6vL1EvT!JqAq#X=O~#!JHQ#QVSPuOGlnLrXXB~{{FsGRq?o?I;>^GFEhMB zw;z!v1sXap8nq3zz&+prKs-DRPm*XsS4BaP6Z{8tM~n@m|rxMA=p6*i(w=7 z*2&*Yg-uWU$5|W>>g5h)Fn{3B={`skAJ5_wXB5pDwyj{vG1_{{Y-`wB_i^B!5PA|= zrx=_>rprb&75BQ=J)SKPAJI;?(D#46)o+a?SsR^-&qJjXY2ER8S*1ZvU`t7~M6?NKULuzlAZ8C#X9>8j2;WDY z(TY-^!`&0%67`u|U_-Y(knWVcSlh-kwZQ6KG@S?L`W!iVl>Gyd(LnpMc@C!QeY{(E z)uAwF_CcqH#00}jer2dQk3}R|p^87XCxR8`n4c@g9rASTt9$8}SuGW!!+QQ&w&G!P zvv5Mft<&pzv^&XuuQAj&ieoa*3nI-hx}0`4kym=(cd>?v6yM3v43y@5@;yPeJ_N{@ z622W$@5Z4VqliMF3GAf_RcB;$HX^%cwTCgxg^4)5I0?*&oW|giBB@nUNBO+IX=iON zo~;L}HOwhyeqH4GHvAQ5i=|0c+_5*661aDyT_tr=I#+Zog%!9nRiuBb8m&SS4qp2fv7HJMG zwJFuqV*Hoq3`|Mayml;So|9W4Um6Lu8(k+(Hc2}p@&>?!7!7H~9*O%@BrKNAOa-~e z$e6#G)fJ+wNz5x9zU;#>&V}d z?!F1W_eNN;&LI9$!kWa0Zqa)0CVM4D=x(r>aXgW=XQ)PTRsJJ&MC?WjjoMwLRh`-I z8yD|^&(r#NU|pRpRF%wn&t%X`)8HQe%uxEKnXxIu9yui1s$eH0*YZ^Wvt25yOg6{5 zPefKstjqam-PRDz=&-BVb^xZe>{C{$cza!_sV&3M*l0ocMJVr!l~TlJi4JChDn9Nn zc&la1caY}0P&Ho=r;)l;mKBf$V<6A*R6XC}s98g%I7ZIAFI=e6SqQ4;oevw)nw0%^ zKq9#$;{3R0zJv}#mr7@}e+5-(`{C?^vEE#xb7uBY=X#_1v+@~@l?W@Zaq+Yo9bpu& zR<0us_T`(Q6qp1xYb)Rq;tJ|aTZ&y5xqx<_j-|>1$SEi@3!A|| z9YH<3ub_#ai=2WG_V9iQ!NU8mB|$4ZK3Gr>_s15;6W-XV-*##3TjwoMP&yb zq!L{!sQoUn<_ZWb)BbzloM2Zs1tb=+FBn*$!EQmp3Ml#oe;g0);^XP&_osni`NR1A z0SL>FG{F)8;h%d#4-g0eK+%&0UD-=ghUr~yDQ?!lNE5tKiJ_rjY{@`Q1vjbVAFU;|?Qs;w|1hFx_ z`*jR7rVAU>9*yRSpD1)#aOb!)@ak(5hk;guG$_9)=K8Ie^uOP<63|FjrX2UEcJw07 zD5c?bxHD${?)1+CMgPg@0|kH>4NzJZO*;#rl-xA_8*SHCS}ygKZP7*uHbRtmaTE%n zp7Vt7QIt|IIN?)fyS#8IxKHO$?TeY{DpQl5^kyAd$HH^Aa)SJC+I0!ULR znF7*z6R6~{CCW6M^qKuU!N`I`>YB3i6toA7f7#3%T&$5&wm0nY{&d9(g)LB$%g9dX zf>HfjVn9;)rG-^=)tiGDd<5M4wDHPl@yEGU_whSh78l$%S*WCqjvj^Xt?_VKp0T{pQGU!F;?_^4EMT$__$E zH0hMGQlo@W2p^_tPZsnirl@pGb<#0a^*g5ihYtSzKKx%Wg;i4h8B_c6Z+PPWM!I%g zOr-dLp|0@RV@@&InVrwRJfPT~ZY840gT$Jl4)HP^qcTUWE~1&}C2wS3Sv9pJWiRva zyK}a9ilnrYe7SB$bu~GF&GM`D1h@ukNsJY|Yt>|?q(4gzgSUuGwSIfsmlD)%J2V0@ zTU&-58&x%P)-#Oev2~&}bv^wwRbD$?Enu(jJiuwM3shGOZ{$juY+RGk#m^`!p7+vO zAjWFn1{dq`T?N^TggHmN3~VGf^5?a_)R-cj5yfk-?V<|S)%uKn{YGL)7(~eAhWA56 zj7ZS7amp#qQM;t>%6F)v{1S-Gq>88IPiL?2X9=q_r$vhc4{Pd3$WssBMbZaV2W zu&8||{U99-3!x+JudoA1KSAx^0qg$*YLr)FKtJ($lC@k)W?khPY!~B&3F~Xnxs_WH)b*(MC{~@>r={U4@A6+2p8il>0lojdT`r8~C>rA6;jw^lZK9gk<_y!v za(Rbclc{1;TFBtT`lr|YO0}|UXzh>FLsx6RQUq8=?V4{NR#=oxL2}kHb-ZAfuN Date: Fri, 6 Sep 2024 13:18:33 -0400 Subject: [PATCH 004/122] Removes node_modules entry from gitingore Signed-off-by: Darshit Chanpura --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index 6fbfafabac..5eb2da999f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,3 @@ out/ build/ gradle-build/ .gradle/ - -# nodejs -node_modules/ -package-lock.json From 45d4fa580684cec31acb434f9d251d64d88643b1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 13:47:23 -0400 Subject: [PATCH 005/122] Handles changes related to scope Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 4 ++-- .../security/resources/ResourceAccessHandler.java | 4 ++-- .../resources/ResourceManagementRepository.java | 11 +++++++++++ .../resources/ResourceSharingIndexHandler.java | 14 ++------------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 27e89f5c31..62c5cad9fe 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2198,8 +2198,8 @@ public List listAccessibleResourcesForPlugin(String systemIndexName) { } @Override - public boolean hasPermission(String resourceId, String systemIndexName) { - return this.resourceAccessHandler.hasPermission(resourceId, systemIndexName); + public boolean hasPermission(String resourceId, String systemIndexName, String scope) { + return this.resourceAccessHandler.hasPermission(resourceId, systemIndexName, scope); } @Override diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 0861854e13..142c6b67da 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -51,9 +51,9 @@ public List listAccessibleResourcesForPlugin(String systemIndex) { return List.of(); } - public boolean hasPermission(String resourceId, String systemIndexName) { + public boolean hasPermission(String resourceId, String systemIndexName, String scope) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Checking if {} has permission to resource {}", user.getName(), resourceId); + LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); // TODO add concrete implementation return false; diff --git a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java index df59516a41..7e347a331d 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java +++ b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java @@ -1,3 +1,14 @@ +/* + * 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.security.resources; import org.apache.logging.log4j.LogManager; diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 79ef85e7eb..b6f4b02ade 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -71,20 +71,10 @@ public void createIndex(ActionListener listener) { } } - // public void createIndexIfAbsent() { - // try { - // final Map indexSettings = ImmutableMap.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); - // final CreateIndexRequest createIndexRequest = new CreateIndexRequest(resourceSharingIndex).settings(indexSettings); - // final boolean ok = client.admin().indices().create(createIndexRequest).actionGet().isAcknowledged(); - // LOGGER.info("Resource sharing index {} created?: {}", resourceSharingIndex, ok); - // } catch (ResourceAlreadyExistsException resourceAlreadyExistsException) { - // LOGGER.info("Index {} already exists", resourceSharingIndex); - // } - // } - public void createResourceSharingIndexIfAbsent(Callable callable) { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { - CreateIndexRequest cir = new CreateIndexRequest(resourceSharingIndex); + CreateIndexRequest cir = new CreateIndexRequest(resourceSharingIndex).settings(INDEX_SETTINGS).waitForActiveShards(1); ActionListener cirListener = ActionListener.wrap(response -> { LOGGER.info("Resource sharing index {} created.", resourceSharingIndex); callable.call(); From ae2464dc83132b21979cae858676fd66861b5c34 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 13:47:47 -0400 Subject: [PATCH 006/122] Updates sample plugin to implement a custom scope Signed-off-by: Darshit Chanpura --- .../security/sample/SampleResourceScope.java | 22 +++++++++++++++++++ .../CreateResourceTransportAction.java | 21 +++++++++++------- 2 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java new file mode 100644 index 0000000000..797f3e517b --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java @@ -0,0 +1,22 @@ +package org.opensearch.security.sample; + +import org.opensearch.accesscontrol.resources.ResourceAccessScope; + +/** + * This class demonstrates a sample implementation of Basic Access Scopes to fit each plugin's use-case. + * The plugin then uses this scope when seeking access evaluation for a user on a particular resource. + */ +enum SampleResourceScope implements ResourceAccessScope { + + SAMPLE_FULL_ACCESS("sample_full_access"); + + private final String name; + + SampleResourceScope(String scopeName) { + this.name = scopeName; + } + + public String getName() { + return name; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java index f95e2d5d5a..dea075c55e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java @@ -9,13 +9,14 @@ package org.opensearch.security.sample.transport; import java.io.IOException; -import java.util.Map; +import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.index.IndexRequest; @@ -86,18 +87,22 @@ private void createResource(CreateResourceRequest request, ActionListener irListener = ActionListener.wrap(idxResponse -> { - log.info("Created resource: {}", idxResponse.toString()); - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - ResourceSharing sharing = rs.getResourceAccessControlPlugin() - .shareWith(idxResponse.getId(), idxResponse.getIndex(), Map.of()); - log.info("Created resource sharing entry: {}", sharing.toString()); - }, listener::onFailure); + ActionListener irListener = getIndexResponseActionListener(listener); nodeClient.index(ir, irListener); } catch (IOException e) { throw new RuntimeException(e); } } + private static ActionListener getIndexResponseActionListener(ActionListener listener) { + ShareWith shareWith = new ShareWith(List.of()); + return ActionListener.wrap(idxResponse -> { + log.info("Created resource: {}", idxResponse.toString()); + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + ResourceSharing sharing = rs.getResourceAccessControlPlugin().shareWith(idxResponse.getId(), idxResponse.getIndex(), shareWith); + log.info("Created resource sharing entry: {}", sharing.toString()); + }, listener::onFailure); + } + // TODO add delete implementation as a separate transport action } From aea2253a4c19089479394a12fde56ea1e555fae8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 13:57:17 -0400 Subject: [PATCH 007/122] Fixes Checkstyle and spotless issues Signed-off-by: Darshit Chanpura --- .../java/org/opensearch/security/sample/Resource.java | 11 +++++++++++ .../security/sample/SampleResourceScope.java | 11 +++++++++++ .../sample/actions/create/SampleResource.java | 11 +++++++++++ .../opensearch/security/OpenSearchSecurityPlugin.java | 5 ++++- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java index 6126fdb092..0dd3b856bf 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java @@ -1,3 +1,14 @@ +/* + * 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.security.sample; import org.opensearch.core.common.io.stream.NamedWriteable; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java index 797f3e517b..7a1b304371 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java @@ -1,3 +1,14 @@ +/* + * 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.security.sample; import org.opensearch.accesscontrol.resources.ResourceAccessScope; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java index 6bc91c369a..50c013f7dc 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java @@ -1,3 +1,14 @@ +/* + * 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.security.sample.actions.create; import java.io.IOException; diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 62c5cad9fe..32a412653f 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -64,7 +64,10 @@ import org.apache.lucene.search.QueryCachingPolicy; import org.apache.lucene.search.Weight; -import org.opensearch.*; +import org.opensearch.OpenSearchException; +import org.opensearch.OpenSearchSecurityException; +import org.opensearch.SpecialPermission; +import org.opensearch.Version; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; From 1e17dc0b0bd74ec726f68526f3597cbe62550163 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 14:12:38 -0400 Subject: [PATCH 008/122] Fixes initialization error Signed-off-by: Darshit Chanpura --- .../org/opensearch/security/OpenSearchSecurityPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 32a412653f..0a0df9e574 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -493,8 +493,6 @@ public List run() { } } - - this.resourceAccessHandler = new ResourceAccessHandler(threadPool); } private void verifyTLSVersion(final String settings, final List configuredProtocols) { @@ -1211,6 +1209,8 @@ public Collection createComponents( e.subscribeForChanges(dcf); } + resourceAccessHandler = new ResourceAccessHandler(threadPool); + rmr = ResourceManagementRepository.create(settings, threadPool, localClient); components.add(adminDns); From 84746e815b4aff72902bbc6a8e9ad467b594209d Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 14:50:46 -0400 Subject: [PATCH 009/122] Renames sample resource plugin and adds a logger statement Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 5 ++--- .../org/opensearch/security/sample/SampleResourcePlugin.java | 3 ++- settings.gradle | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle index 6d4b084580..dd04d390b0 100644 --- a/sample-resource-plugin/build.gradle +++ b/sample-resource-plugin/build.gradle @@ -11,10 +11,9 @@ import org.opensearch.gradle.test.RestIntegTestTask opensearchplugin { - name 'opensearch-security-sample-resource-plugin' + name 'opensearch-sample-resource-plugin' description 'Sample plugin that extends OpenSearch Resource Plugin' - classname 'org.opensearch.security.sampleresourceplugin.SampleResourcePlugin' - extendedPlugins = ['opensearch-security'] + classname 'org.opensearch.security.sample.SampleResourcePlugin' } ext { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java index 58e4daa95c..5d598c5650 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java @@ -81,6 +81,7 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; + log.info("Loaded SampleResourcePlugin components."); return Collections.emptyList(); } @@ -118,7 +119,7 @@ public String getResourceType() { @Override public String getResourceIndex() { - return ""; + return RESOURCE_INDEX_NAME; } @Override diff --git a/settings.gradle b/settings.gradle index f2e59414d8..0bb3c5639d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,4 +7,4 @@ rootProject.name = 'opensearch-security' include "sample-resource-plugin" -project(":sample-resource-plugin").name = rootProject.name + "-sample-resource-plugin" +project(":sample-resource-plugin").name = "opensearch-sample-resource-plugin" From 83e4da09ce210143db68b3464dc0dc92e3bbf3cf Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 14:53:10 -0400 Subject: [PATCH 010/122] Changes package name for sample plugin Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 2 +- .../opensearch/{security => }/sample/Resource.java | 2 +- .../sample/SampleResourcePlugin.java | 14 +++++++------- .../{security => }/sample/SampleResourceScope.java | 2 +- .../actions/create/CreateSampleResourceAction.java | 4 ++-- .../create/CreateSampleResourceRequest.java | 4 ++-- .../create/CreateSampleResourceResponse.java | 2 +- .../create/CreateSampleResourceRestAction.java | 4 ++-- .../CreateSampleResourceTransportAction.java | 6 +++--- .../sample/actions/create/SampleResource.java | 6 +++--- .../actions/list/ListSampleResourceAction.java | 2 +- .../actions/list/ListSampleResourceRequest.java | 2 +- .../actions/list/ListSampleResourceResponse.java | 2 +- .../actions/list/ListSampleResourceRestAction.java | 2 +- .../list/ListSampleResourceTransportAction.java | 2 +- .../sample/transport/CreateResourceRequest.java | 4 ++-- .../sample/transport/CreateResourceResponse.java | 2 +- .../transport/CreateResourceTransportAction.java | 6 +++--- 18 files changed, 34 insertions(+), 34 deletions(-) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/Resource.java (93%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/SampleResourcePlugin.java (91%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/SampleResourceScope.java (95%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/create/CreateSampleResourceAction.java (85%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/create/CreateSampleResourceRequest.java (92%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/create/CreateSampleResourceResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/create/CreateSampleResourceRestAction.java (93%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/create/CreateSampleResourceTransportAction.java (82%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/create/SampleResource.java (87%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/list/ListSampleResourceAction.java (93%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/list/ListSampleResourceRequest.java (95%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/list/ListSampleResourceResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/list/ListSampleResourceRestAction.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/actions/list/ListSampleResourceTransportAction.java (97%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/transport/CreateResourceRequest.java (92%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/transport/CreateResourceResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/{security => }/sample/transport/CreateResourceTransportAction.java (96%) diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle index dd04d390b0..e9822c1f22 100644 --- a/sample-resource-plugin/build.gradle +++ b/sample-resource-plugin/build.gradle @@ -13,7 +13,7 @@ import org.opensearch.gradle.test.RestIntegTestTask opensearchplugin { name 'opensearch-sample-resource-plugin' description 'Sample plugin that extends OpenSearch Resource Plugin' - classname 'org.opensearch.security.sample.SampleResourcePlugin' + classname 'org.opensearch.sample.SampleResourcePlugin' } ext { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java similarity index 93% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java index 0dd3b856bf..36e74f1624 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/Resource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java @@ -9,7 +9,7 @@ * GitHub history for details. */ -package org.opensearch.security.sample; +package org.opensearch.sample; import org.opensearch.core.common.io.stream.NamedWriteable; import org.opensearch.core.xcontent.ToXContentFragment; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java similarity index 91% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 5d598c5650..bb272b2201 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -6,7 +6,7 @@ * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ -package org.opensearch.security.sample; +package org.opensearch.sample; import java.util.ArrayList; import java.util.Collection; @@ -44,13 +44,13 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; +import org.opensearch.sample.actions.create.CreateSampleResourceAction; +import org.opensearch.sample.actions.create.CreateSampleResourceRestAction; +import org.opensearch.sample.actions.create.CreateSampleResourceTransportAction; +import org.opensearch.sample.actions.list.ListSampleResourceAction; +import org.opensearch.sample.actions.list.ListSampleResourceRestAction; +import org.opensearch.sample.actions.list.ListSampleResourceTransportAction; import org.opensearch.script.ScriptService; -import org.opensearch.security.sample.actions.create.CreateSampleResourceAction; -import org.opensearch.security.sample.actions.create.CreateSampleResourceRestAction; -import org.opensearch.security.sample.actions.create.CreateSampleResourceTransportAction; -import org.opensearch.security.sample.actions.list.ListSampleResourceAction; -import org.opensearch.security.sample.actions.list.ListSampleResourceRestAction; -import org.opensearch.security.sample.actions.list.ListSampleResourceTransportAction; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java similarity index 95% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java index 7a1b304371..2784de45b7 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/SampleResourceScope.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java @@ -9,7 +9,7 @@ * GitHub history for details. */ -package org.opensearch.security.sample; +package org.opensearch.sample; import org.opensearch.accesscontrol.resources.ResourceAccessScope; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceAction.java similarity index 85% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceAction.java index 1e106d1a47..fce62be629 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceAction.java @@ -6,10 +6,10 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.create; +package org.opensearch.sample.actions.create; import org.opensearch.action.ActionType; -import org.opensearch.security.sample.transport.CreateResourceResponse; +import org.opensearch.sample.transport.CreateResourceResponse; /** * Action to create a sample resource diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java similarity index 92% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java index 35815f9a17..a509031b0b 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.create; +package org.opensearch.sample.actions.create; import java.io.IOException; @@ -14,7 +14,7 @@ import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.security.sample.Resource; +import org.opensearch.sample.Resource; /** * Request object for CreateSampleResource transport action diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceResponse.java index 476d63d5fe..86796bfff5 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.create; +package org.opensearch.sample.actions.create; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRestAction.java similarity index 93% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRestAction.java index 00e41bbdf9..f422835168 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRestAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.create; +package org.opensearch.sample.actions.create; import java.io.IOException; import java.util.List; @@ -17,7 +17,7 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.security.sample.transport.CreateResourceRequest; +import org.opensearch.sample.transport.CreateResourceRequest; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.POST; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java similarity index 82% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java index 23c84aec82..53d9817fbc 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/CreateSampleResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.create; +package org.opensearch.sample.actions.create; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,10 +14,10 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.client.Client; import org.opensearch.common.inject.Inject; -import org.opensearch.security.sample.transport.CreateResourceTransportAction; +import org.opensearch.sample.transport.CreateResourceTransportAction; import org.opensearch.transport.TransportService; -import static org.opensearch.security.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; /** * Transport action for CreateSampleResource. diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java similarity index 87% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java index 50c013f7dc..d2528c92be 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/create/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java @@ -9,16 +9,16 @@ * GitHub history for details. */ -package org.opensearch.security.sample.actions.create; +package org.opensearch.sample.actions.create; import java.io.IOException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.security.sample.Resource; +import org.opensearch.sample.Resource; -import static org.opensearch.security.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; public class SampleResource extends Resource { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceAction.java similarity index 93% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceAction.java index 89bee6c093..17f50cda30 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.list; +package org.opensearch.sample.actions.list; import org.opensearch.action.ActionType; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRequest.java similarity index 95% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRequest.java index 27d1cd6cfd..ffadf6abbb 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.list; +package org.opensearch.sample.actions.list; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceResponse.java index 021d456cab..aaf6bfcd3e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.list; +package org.opensearch.sample.actions.list; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRestAction.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRestAction.java index e56fd08179..3f01bb5e2c 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRestAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.list; +package org.opensearch.sample.actions.list; import java.util.List; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java similarity index 97% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java index e04435725e..ece829fe0d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/actions/list/ListSampleResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.actions.list; +package org.opensearch.sample.actions.list; import org.opensearch.action.search.SearchRequest; import org.opensearch.action.search.SearchResponse; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceRequest.java similarity index 92% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceRequest.java index ea1eb57755..f23735e7f3 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.transport; +package org.opensearch.sample.transport; import java.io.IOException; @@ -14,7 +14,7 @@ import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.security.sample.Resource; +import org.opensearch.sample.Resource; /** * Request object for CreateSampleResource transport action diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceResponse.java index 892cd74108..12d7671ac4 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.transport; +package org.opensearch.sample.transport; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index dea075c55e..5e2eb6d723 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/security/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.security.sample.transport; +package org.opensearch.sample.transport; import java.io.IOException; import java.util.List; @@ -29,8 +29,8 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.common.io.stream.Writeable; import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.security.sample.Resource; -import org.opensearch.security.sample.SampleResourcePlugin; +import org.opensearch.sample.Resource; +import org.opensearch.sample.SampleResourcePlugin; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; From 4b9b9b13bb79029a1fb96e8e8d38525e3aad1ba8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 18:10:57 -0400 Subject: [PATCH 011/122] Re-organizes and renames sample plugin files Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePlugin.java | 33 ++++---- ...eAction.java => CreateResourceAction.java} | 7 +- .../create}/CreateResourceRequest.java | 12 +-- .../create}/CreateResourceResponse.java | 2 +- ...ion.java => CreateResourceRestAction.java} | 11 ++- .../create/CreateSampleResourceRequest.java | 55 ------------- .../CreateSampleResourceTransportAction.java | 32 -------- .../sample/actions/create/SampleResource.java | 2 +- ...ava => ListAccessibleResourcesAction.java} | 8 +- ...va => ListAccessibleResourcesRequest.java} | 6 +- .../list/ListAccessibleResourcesResponse.java | 46 +++++++++++ ...=> ListAccessibleResourcesRestAction.java} | 12 +-- .../ListSampleResourceTransportAction.java | 52 ------------- .../actions/share/ShareResourceAction.java | 26 +++++++ .../actions/share/ShareResourceRequest.java | 52 +++++++++++++ .../ShareResourceResponse.java} | 11 +-- .../share/ShareResourceRestAction.java | 51 ++++++++++++ .../verify/VerifyResourceAccessAction.java | 25 ++++++ .../verify/VerifyResourceAccessRequest.java | 69 +++++++++++++++++ .../VerifyResourceAccessResponse.java} | 11 +-- .../VerifyResourceAccessRestAction.java | 52 +++++++++++++ .../CreateResourceTransportAction.java | 32 +++----- ...istAccessibleResourcesTransportAction.java | 56 ++++++++++++++ .../ShareResourceTransportAction.java | 77 +++++++++++++++++++ .../VerifyResourceAccessTransportAction.java | 58 ++++++++++++++ .../resources/ResourceAccessHandler.java | 6 +- 26 files changed, 583 insertions(+), 221 deletions(-) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/{CreateSampleResourceAction.java => CreateResourceAction.java} (67%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/{transport => actions/create}/CreateResourceRequest.java (73%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/{transport => actions/create}/CreateResourceResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/{CreateSampleResourceRestAction.java => CreateResourceRestAction.java} (75%) delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/{ListSampleResourceAction.java => ListAccessibleResourcesAction.java} (63%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/{ListSampleResourceRequest.java => ListAccessibleResourcesRequest.java} (81%) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/{ListSampleResourceRestAction.java => ListAccessibleResourcesRestAction.java} (68%) delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{list/ListSampleResourceResponse.java => share/ShareResourceResponse.java} (78%) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{create/CreateSampleResourceResponse.java => verify/VerifyResourceAccessResponse.java} (81%) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index bb272b2201..abc9ed4de7 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -8,10 +8,7 @@ */ package org.opensearch.sample; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; @@ -44,12 +41,16 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; -import org.opensearch.sample.actions.create.CreateSampleResourceAction; -import org.opensearch.sample.actions.create.CreateSampleResourceRestAction; -import org.opensearch.sample.actions.create.CreateSampleResourceTransportAction; -import org.opensearch.sample.actions.list.ListSampleResourceAction; -import org.opensearch.sample.actions.list.ListSampleResourceRestAction; -import org.opensearch.sample.actions.list.ListSampleResourceTransportAction; +import org.opensearch.sample.actions.create.CreateResourceAction; +import org.opensearch.sample.actions.create.CreateResourceRestAction; +import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; +import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction; +import org.opensearch.sample.actions.share.ShareResourceAction; +import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; +import org.opensearch.sample.transport.CreateResourceTransportAction; +import org.opensearch.sample.transport.ListAccessibleResourcesTransportAction; +import org.opensearch.sample.transport.ShareResourceTransportAction; +import org.opensearch.sample.transport.VerifyResourceAccessTransportAction; import org.opensearch.script.ScriptService; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; @@ -62,7 +63,9 @@ public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); - public static final String RESOURCE_INDEX_NAME = ".sample_resources"; + public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; + + public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); private Client client; @@ -95,14 +98,16 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - return List.of(new CreateSampleResourceRestAction(), new ListSampleResourceRestAction()); + return List.of(new CreateResourceRestAction(), new ListAccessibleResourcesRestAction()); } @Override public List> getActions() { return List.of( - new ActionHandler<>(CreateSampleResourceAction.INSTANCE, CreateSampleResourceTransportAction.class), - new ActionHandler<>(ListSampleResourceAction.INSTANCE, ListSampleResourceTransportAction.class) + new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class), + new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class), + new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class), + new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class) ); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java similarity index 67% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java index fce62be629..5ddcc79008 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java @@ -9,22 +9,21 @@ package org.opensearch.sample.actions.create; import org.opensearch.action.ActionType; -import org.opensearch.sample.transport.CreateResourceResponse; /** * Action to create a sample resource */ -public class CreateSampleResourceAction extends ActionType { +public class CreateResourceAction extends ActionType { /** * Create sample resource action instance */ - public static final CreateSampleResourceAction INSTANCE = new CreateSampleResourceAction(); + public static final CreateResourceAction INSTANCE = new CreateResourceAction(); /** * Create sample resource action name */ public static final String NAME = "cluster:admin/sampleresource/create"; - private CreateSampleResourceAction() { + private CreateResourceAction() { super(NAME, CreateResourceResponse::new); } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java similarity index 73% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java index f23735e7f3..b31a4b7f2b 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.actions.create; import java.io.IOException; @@ -19,19 +19,19 @@ /** * Request object for CreateSampleResource transport action */ -public class CreateResourceRequest extends ActionRequest { +public class CreateResourceRequest extends ActionRequest { - private final T resource; + private final Resource resource; /** * Default constructor */ - public CreateResourceRequest(T resource) { + public CreateResourceRequest(Resource resource) { this.resource = resource; } - public CreateResourceRequest(StreamInput in, Reader resourceReader) throws IOException { - this.resource = resourceReader.read(in); + public CreateResourceRequest(StreamInput in) throws IOException { + this.resource = in.readNamedWriteable(Resource.class); } @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java index 12d7671ac4..6b966ed08d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.actions.create; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java similarity index 75% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java index f422835168..86346cc279 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java @@ -17,18 +17,17 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.sample.transport.CreateResourceRequest; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.POST; -public class CreateSampleResourceRestAction extends BaseRestHandler { +public class CreateResourceRestAction extends BaseRestHandler { - public CreateSampleResourceRestAction() {} + public CreateResourceRestAction() {} @Override public List routes() { - return singletonList(new Route(POST, "/_plugins/resource_sharing_example/resource")); + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/resource")); } @Override @@ -46,9 +45,9 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client String name = (String) source.get("name"); SampleResource resource = new SampleResource(); resource.setName(name); - final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest<>(resource); + final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest(resource); return channel -> client.executeLocally( - CreateSampleResourceAction.INSTANCE, + CreateResourceAction.INSTANCE, createSampleResourceRequest, new RestToXContentListener<>(channel) ); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java deleted file mode 100644 index a509031b0b..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.create; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.Resource; - -/** - * Request object for CreateSampleResource transport action - */ -public class CreateSampleResourceRequest extends ActionRequest { - - private final Resource resource; - - /** - * Default constructor - */ - public CreateSampleResourceRequest(Resource resource) { - this.resource = resource; - } - - /** - * Constructor with stream input - * @param in the stream input - * @throws IOException IOException - */ - public CreateSampleResourceRequest(final StreamInput in) throws IOException { - this.resource = new SampleResource(in); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - resource.writeTo(out); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public Resource getResource() { - return this.resource; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java deleted file mode 100644 index 53d9817fbc..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceTransportAction.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.create; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.action.support.ActionFilters; -import org.opensearch.client.Client; -import org.opensearch.common.inject.Inject; -import org.opensearch.sample.transport.CreateResourceTransportAction; -import org.opensearch.transport.TransportService; - -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; - -/** - * Transport action for CreateSampleResource. - */ -public class CreateSampleResourceTransportAction extends CreateResourceTransportAction { - private static final Logger log = LogManager.getLogger(CreateSampleResourceTransportAction.class); - - @Inject - public CreateSampleResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { - super(transportService, actionFilters, nodeClient, CreateSampleResourceAction.NAME, RESOURCE_INDEX_NAME, SampleResource::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java index d2528c92be..1566abfe69 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java @@ -47,7 +47,7 @@ public void writeTo(StreamOutput streamOutput) throws IOException { @Override public String getWriteableName() { - return "sampled_resource"; + return "sample_resource"; } public void setName(String name) { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java similarity index 63% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java index 17f50cda30..cc7e4769f6 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java @@ -13,17 +13,17 @@ /** * Action to list sample resources */ -public class ListSampleResourceAction extends ActionType { +public class ListAccessibleResourcesAction extends ActionType { /** * List sample resource action instance */ - public static final ListSampleResourceAction INSTANCE = new ListSampleResourceAction(); + public static final ListAccessibleResourcesAction INSTANCE = new ListAccessibleResourcesAction(); /** * List sample resource action name */ public static final String NAME = "cluster:admin/sampleresource/list"; - private ListSampleResourceAction() { - super(NAME, ListSampleResourceResponse::new); + private ListAccessibleResourcesAction() { + super(NAME, ListAccessibleResourcesResponse::new); } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java similarity index 81% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java index ffadf6abbb..b4c0961774 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java @@ -18,16 +18,16 @@ /** * Request object for ListSampleResource transport action */ -public class ListSampleResourceRequest extends ActionRequest { +public class ListAccessibleResourcesRequest extends ActionRequest { - public ListSampleResourceRequest() {} + public ListAccessibleResourcesRequest() {} /** * Constructor with stream input * @param in the stream input * @throws IOException IOException */ - public ListSampleResourceRequest(final StreamInput in) throws IOException {} + public ListAccessibleResourcesRequest(final StreamInput in) throws IOException {} @Override public void writeTo(final StreamOutput out) throws IOException {} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java new file mode 100644 index 0000000000..47a8f88e4e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.list; + +import java.io.IOException; +import java.util.List; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +/** + * Response to a ListAccessibleResourcesRequest + */ +public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { + private final List resourceIds; + + public ListAccessibleResourcesResponse(List resourceIds) { + this.resourceIds = resourceIds; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringArray(resourceIds.toArray(new String[0])); + } + + public ListAccessibleResourcesResponse(final StreamInput in) throws IOException { + resourceIds = in.readStringList(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("resource-ids", resourceIds); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java similarity index 68% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java index 3f01bb5e2c..bb921fce00 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java @@ -18,13 +18,13 @@ import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.GET; -public class ListSampleResourceRestAction extends BaseRestHandler { +public class ListAccessibleResourcesRestAction extends BaseRestHandler { - public ListSampleResourceRestAction() {} + public ListAccessibleResourcesRestAction() {} @Override public List routes() { - return singletonList(new Route(GET, "/_plugins/resource_sharing_example/resource")); + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/resource")); } @Override @@ -34,10 +34,10 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { - final ListSampleResourceRequest listSampleResourceRequest = new ListSampleResourceRequest(); + final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(); return channel -> client.executeLocally( - ListSampleResourceAction.INSTANCE, - listSampleResourceRequest, + ListAccessibleResourcesAction.INSTANCE, + listAccessibleResourcesRequest, new RestToXContentListener<>(channel) ); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java deleted file mode 100644 index ece829fe0d..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceTransportAction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.list; - -import org.opensearch.action.search.SearchRequest; -import org.opensearch.action.search.SearchResponse; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.client.Client; -import org.opensearch.common.inject.Inject; -import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.core.action.ActionListener; -import org.opensearch.index.query.MatchAllQueryBuilder; -import org.opensearch.search.builder.SearchSourceBuilder; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -/** - * Transport action for ListSampleResource. - */ -public class ListSampleResourceTransportAction extends HandledTransportAction { - private final TransportService transportService; - private final Client nodeClient; - - @Inject - public ListSampleResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { - super(ListSampleResourceAction.NAME, transportService, actionFilters, ListSampleResourceRequest::new); - this.transportService = transportService; - this.nodeClient = nodeClient; - } - - @Override - protected void doExecute(Task task, ListSampleResourceRequest request, ActionListener listener) { - try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { - SearchRequest sr = new SearchRequest(".resource-sharing"); - SearchSourceBuilder matchAllQuery = new SearchSourceBuilder(); - matchAllQuery.query(new MatchAllQueryBuilder()); - sr.source(matchAllQuery); - /* Index already exists, ignore and continue */ - ActionListener searchListener = ActionListener.wrap(response -> { - listener.onResponse(new ListSampleResourceResponse(response.toString())); - }, listener::onFailure); - nodeClient.search(sr, searchListener); - } - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java new file mode 100644 index 0000000000..152caf8c8c --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import org.opensearch.action.ActionType; + +public class ShareResourceAction extends ActionType { + /** + * List sample resource action instance + */ + public static final ShareResourceAction INSTANCE = new ShareResourceAction(); + /** + * List sample resource action name + */ + public static final String NAME = "cluster:admin/sampleresource/share"; + + private ShareResourceAction() { + super(NAME, ShareResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java new file mode 100644 index 0000000000..01866fd516 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import java.io.IOException; + +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +public class ShareResourceRequest extends ActionRequest { + + private final String resourceId; + private final ShareWith shareWith; + + public ShareResourceRequest(String resourceId, ShareWith shareWith) { + this.resourceId = resourceId; + this.shareWith = shareWith; + } + + public ShareResourceRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.shareWith = in.readNamedWriteable(ShareWith.class); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeNamedWriteable(shareWith); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public ShareWith getShareWith() { + return shareWith; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java similarity index 78% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java index aaf6bfcd3e..a6a85d206d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListSampleResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.list; +package org.opensearch.sample.actions.share; import java.io.IOException; @@ -16,10 +16,7 @@ import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.core.xcontent.XContentBuilder; -/** - * Response to a ListSampleResourceRequest - */ -public class ListSampleResourceResponse extends ActionResponse implements ToXContentObject { +public class ShareResourceResponse extends ActionResponse implements ToXContentObject { private final String message; /** @@ -27,7 +24,7 @@ public class ListSampleResourceResponse extends ActionResponse implements ToXCon * * @param message The message */ - public ListSampleResourceResponse(String message) { + public ShareResourceResponse(String message) { this.message = message; } @@ -41,7 +38,7 @@ public void writeTo(StreamOutput out) throws IOException { * * @param in the stream input */ - public ListSampleResourceResponse(final StreamInput in) throws IOException { + public ShareResourceResponse(final StreamInput in) throws IOException { message = in.readString(); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java new file mode 100644 index 0000000000..87bc083f2e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class ShareResourceRestAction extends BaseRestHandler { + + public ShareResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/share/{resource_id}")); + } + + @Override + public String getName() { + return "list_sample_resources"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + ShareWith shareWith = (ShareWith) source.get("share_with"); + final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, shareWith); + return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java new file mode 100644 index 0000000000..2e57786a13 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import org.opensearch.action.ActionType; + +/** + * Action to verify resource access for current user + */ +public class VerifyResourceAccessAction extends ActionType { + + public static final VerifyResourceAccessAction INSTANCE = new VerifyResourceAccessAction(); + + public static final String NAME = "cluster:admin/sampleresource/verify/resource_access"; + + private VerifyResourceAccessAction() { + super(NAME, VerifyResourceAccessResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java new file mode 100644 index 0000000000..e9b20118db --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java @@ -0,0 +1,69 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +public class VerifyResourceAccessRequest extends ActionRequest { + + private final String resourceId; + + private final String sourceIdx; + + private final String scope; + + /** + * Default constructor + */ + public VerifyResourceAccessRequest(String resourceId, String sourceIdx, String scope) { + this.resourceId = resourceId; + this.sourceIdx = sourceIdx; + this.scope = scope; + } + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public VerifyResourceAccessRequest(final StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.sourceIdx = in.readString(); + this.scope = in.readString(); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeString(sourceIdx); + out.writeString(scope); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public String getSourceIdx() { + return sourceIdx; + } + + public String getScope() { + return scope; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java similarity index 81% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java index 86796bfff5..660ac03f71 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateSampleResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.create; +package org.opensearch.sample.actions.verify; import java.io.IOException; @@ -16,10 +16,7 @@ import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.core.xcontent.XContentBuilder; -/** - * Response to a CreateSampleResourceRequest - */ -public class CreateSampleResourceResponse extends ActionResponse implements ToXContentObject { +public class VerifyResourceAccessResponse extends ActionResponse implements ToXContentObject { private final String message; /** @@ -27,7 +24,7 @@ public class CreateSampleResourceResponse extends ActionResponse implements ToXC * * @param message The message */ - public CreateSampleResourceResponse(String message) { + public VerifyResourceAccessResponse(String message) { this.message = message; } @@ -41,7 +38,7 @@ public void writeTo(StreamOutput out) throws IOException { * * @param in the stream input */ - public CreateSampleResourceResponse(final StreamInput in) throws IOException { + public VerifyResourceAccessResponse(final StreamInput in) throws IOException { message = in.readString(); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java new file mode 100644 index 0000000000..34bfed4e9f --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.POST; + +public class VerifyResourceAccessRestAction extends BaseRestHandler { + + public VerifyResourceAccessRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/verify_resource_access")); + } + + @Override + public String getName() { + return "verify_resource_access"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceIdx = (String) source.get("resource_idx"); + String sourceIdx = (String) source.get("source_idx"); + String scope = (String) source.get("scope"); + + // final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest<>(resource); + return channel -> client.executeLocally(VerifyResourceAccessAction.INSTANCE, null, new RestToXContentListener<>(channel)); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index 5e2eb6d723..d3bb8f19b2 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -17,8 +17,6 @@ import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.action.admin.indices.create.CreateIndexRequest; -import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.ActionFilters; @@ -27,10 +25,11 @@ import org.opensearch.client.Client; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; -import org.opensearch.core.common.io.stream.Writeable; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.sample.Resource; import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.create.CreateResourceRequest; +import org.opensearch.sample.actions.create.CreateResourceResponse; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -39,9 +38,7 @@ /** * Transport action for CreateSampleResource. */ -public class CreateResourceTransportAction extends HandledTransportAction< - CreateResourceRequest, - CreateResourceResponse> { +public class CreateResourceTransportAction extends HandledTransportAction { private static final Logger log = LogManager.getLogger(CreateResourceTransportAction.class); private final TransportService transportService; @@ -53,31 +50,25 @@ public CreateResourceTransportAction( ActionFilters actionFilters, Client nodeClient, String actionName, - String resourceIndex, - Writeable.Reader resourceReader + String resourceIndex ) { - super(actionName, transportService, actionFilters, (in) -> new CreateResourceRequest(in, resourceReader)); + super(actionName, transportService, actionFilters, (in) -> new CreateResourceRequest(in)); this.transportService = transportService; this.nodeClient = nodeClient; this.resourceIndex = resourceIndex; } @Override - protected void doExecute(Task task, CreateResourceRequest request, ActionListener listener) { + protected void doExecute(Task task, CreateResourceRequest request, ActionListener listener) { try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { - CreateIndexRequest cir = new CreateIndexRequest(resourceIndex); - ActionListener cirListener = ActionListener.wrap( - response -> { createResource(request, listener); }, - (failResponse) -> { - /* Index already exists, ignore and continue */ - createResource(request, listener); - } - ); - nodeClient.admin().indices().create(cir, cirListener); + createResource(request, listener); + listener.onResponse(new CreateResourceResponse("Resource " + request.getResource() + " created successfully.")); + } catch (Exception e) { + listener.onFailure(e); } } - private void createResource(CreateResourceRequest request, ActionListener listener) { + private void createResource(CreateResourceRequest request, ActionListener listener) { Resource sample = request.getResource(); try { IndexRequest ir = nodeClient.prepareIndex(resourceIndex) @@ -104,5 +95,4 @@ private static ActionListener getIndexResponseActionListener(Acti }, listener::onFailure); } - // TODO add delete implementation as a separate transport action } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java new file mode 100644 index 0000000000..c4734ad928 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; +import org.opensearch.sample.actions.list.ListAccessibleResourcesRequest; +import org.opensearch.sample.actions.list.ListAccessibleResourcesResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for ListSampleResource. + */ +public class ListAccessibleResourcesTransportAction extends HandledTransportAction< + ListAccessibleResourcesRequest, + ListAccessibleResourcesResponse> { + private static final Logger log = LogManager.getLogger(ListAccessibleResourcesTransportAction.class); + + @Inject + public ListAccessibleResourcesTransportAction(TransportService transportService, ActionFilters actionFilters) { + super(ListAccessibleResourcesAction.NAME, transportService, actionFilters, ListAccessibleResourcesRequest::new); + } + + @Override + protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { + try { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesForPlugin(RESOURCE_INDEX_NAME); + log.info("Successfully fetched accessible resources for current user"); + listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); + } catch (Exception e) { + log.info("Failed to list accessible resources for current user: ", e); + listener.onFailure(e); + } + + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java new file mode 100644 index 0000000000..0dfab3fade --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java @@ -0,0 +1,77 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.share.ShareResourceRequest; +import org.opensearch.sample.actions.share.ShareResourceResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for CreateSampleResource. + */ +public class ShareResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); + + private final TransportService transportService; + private final Client nodeClient; + private final String resourceIndex; + + public ShareResourceTransportAction( + TransportService transportService, + ActionFilters actionFilters, + Client nodeClient, + String actionName, + String resourceIndex + ) { + super(actionName, transportService, actionFilters, ShareResourceRequest::new); + this.transportService = transportService; + this.nodeClient = nodeClient; + this.resourceIndex = resourceIndex; + } + + @Override + protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { + try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + shareResource(request); + listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); + } catch (Exception e) { + listener.onFailure(e); + } + } + + private void shareResource(ShareResourceRequest request) { + try { + ShareWith shareWith = new ShareWith(List.of()); + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + ResourceSharing sharing = rs.getResourceAccessControlPlugin() + .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, shareWith); + log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); + } catch (Exception e) { + log.info("Failed to share resource {}", request.getResourceId(), e); + throw e; + } + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java new file mode 100644 index 0000000000..947dcec59e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; +import org.opensearch.sample.actions.verify.VerifyResourceAccessRequest; +import org.opensearch.sample.actions.verify.VerifyResourceAccessResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class VerifyResourceAccessTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class); + + @Inject + public VerifyResourceAccessTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(VerifyResourceAccessAction.NAME, transportService, actionFilters, VerifyResourceAccessRequest::new); + } + + @Override + protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) { + try { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + boolean hasRequestedScopeAccess = rs.getResourceAccessControlPlugin() + .hasPermission(request.getResourceId(), request.getSourceIdx(), request.getScope()); + + StringBuilder sb = new StringBuilder(); + sb.append("User does"); + sb.append(hasRequestedScopeAccess ? " " : " not "); + sb.append("have requested scope "); + sb.append(request.getScope()); + sb.append(" access to "); + sb.append(request.getResourceId()); + + log.info(sb.toString()); + listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); + } catch (Exception e) { + log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); + listener.onFailure(e); + } + } + +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 142c6b67da..9c26811dc9 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -17,6 +17,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.accesscontrol.resources.CreatedBy; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; @@ -61,10 +62,11 @@ public boolean hasPermission(String resourceId, String systemIndexName, String s public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith); + LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith); // TODO add concrete implementation - return null; + CreatedBy c = new CreatedBy("", null); + return new ResourceSharing(systemIndexName, resourceId, c, shareWith); } public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { From 81216f17d6b61ef11b4c393362b92ecd2b861477 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 18:24:05 -0400 Subject: [PATCH 012/122] Updates method references to conform to core Signed-off-by: Darshit Chanpura --- .../sample/transport/CreateResourceTransportAction.java | 2 ++ .../ListAccessibleResourcesTransportAction.java | 2 +- .../sample/transport/ShareResourceTransportAction.java | 2 ++ .../opensearch/security/OpenSearchSecurityPlugin.java | 9 ++------- .../security/resources/ResourceAccessHandler.java | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index d3bb8f19b2..44d18ef846 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -23,6 +23,7 @@ import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.ToXContent; @@ -45,6 +46,7 @@ public class CreateResourceTransportAction extends HandledTransportAction listener) { try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesForPlugin(RESOURCE_INDEX_NAME); + List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); log.info("Successfully fetched accessible resources for current user"); listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); } catch (Exception e) { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java index 0dfab3fade..ff1541773e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java @@ -19,6 +19,7 @@ import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; @@ -39,6 +40,7 @@ public class ShareResourceTransportAction extends HandledTransportAction> listAccessibleResources() { - return this.resourceAccessHandler.listAccessibleResources(); - } - - @Override - public List listAccessibleResourcesForPlugin(String systemIndexName) { - return this.resourceAccessHandler.listAccessibleResourcesForPlugin(systemIndexName); + public List listAccessibleResourcesInPlugin(String systemIndexName) { + return this.resourceAccessHandler.listAccessibleResourcesInPlugin(systemIndexName); } @Override diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 9c26811dc9..838785ee7f 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -44,7 +44,7 @@ public Map> listAccessibleResources() { return Map.of(); } - public List listAccessibleResourcesForPlugin(String systemIndex) { + public List listAccessibleResourcesInPlugin(String systemIndex) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Listing accessible resource within a system index {} for : {}", systemIndex, user.getName()); From 1e33dad85da54f7074a2f50715006fd7e30a5e57 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 18:58:31 -0400 Subject: [PATCH 013/122] Fixes compile errors Signed-off-by: Darshit Chanpura --- .../actions/create/CreateResourceAction.java | 2 +- .../list/ListAccessibleResourcesAction.java | 2 +- .../actions/share/ShareResourceAction.java | 2 +- .../verify/VerifyResourceAccessAction.java | 2 +- .../CreateResourceTransportAction.java | 16 +++++--------- .../ShareResourceTransportAction.java | 22 ++++--------------- 6 files changed, 13 insertions(+), 33 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java index 5ddcc79008..e7c02278ab 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java @@ -21,7 +21,7 @@ public class CreateResourceAction extends ActionType { /** * Create sample resource action name */ - public static final String NAME = "cluster:admin/sampleresource/create"; + public static final String NAME = "cluster:admin/sample-resource-plugin/create"; private CreateResourceAction() { super(NAME, CreateResourceResponse::new); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java index cc7e4769f6..b4e9e29e22 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java @@ -21,7 +21,7 @@ public class ListAccessibleResourcesAction extends ActionType { /** * List sample resource action name */ - public static final String NAME = "cluster:admin/sampleresource/share"; + public static final String NAME = "cluster:admin/sample-resource-plugin/share"; private ShareResourceAction() { super(NAME, ShareResourceResponse::new); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java index 2e57786a13..1378d561f5 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java @@ -17,7 +17,7 @@ public class VerifyResourceAccessAction extends ActionType new CreateResourceRequest(in)); + public CreateResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(CreateResourceAction.NAME, transportService, actionFilters, CreateResourceRequest::new); this.transportService = transportService; this.nodeClient = nodeClient; - this.resourceIndex = resourceIndex; } @Override @@ -73,7 +67,7 @@ protected void doExecute(Task task, CreateResourceRequest request, ActionListene private void createResource(CreateResourceRequest request, ActionListener listener) { Resource sample = request.getResource(); try { - IndexRequest ir = nodeClient.prepareIndex(resourceIndex) + IndexRequest ir = nodeClient.prepareIndex(RESOURCE_INDEX_NAME) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .setSource(sample.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) .request(); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java index ff1541773e..ccbfc31b78 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java @@ -18,11 +18,10 @@ import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.client.Client; import org.opensearch.common.inject.Inject; -import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.share.ShareResourceAction; import org.opensearch.sample.actions.share.ShareResourceRequest; import org.opensearch.sample.actions.share.ShareResourceResponse; import org.opensearch.tasks.Task; @@ -36,27 +35,14 @@ public class ShareResourceTransportAction extends HandledTransportAction { private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); - private final TransportService transportService; - private final Client nodeClient; - private final String resourceIndex; - @Inject - public ShareResourceTransportAction( - TransportService transportService, - ActionFilters actionFilters, - Client nodeClient, - String actionName, - String resourceIndex - ) { - super(actionName, transportService, actionFilters, ShareResourceRequest::new); - this.transportService = transportService; - this.nodeClient = nodeClient; - this.resourceIndex = resourceIndex; + public ShareResourceTransportAction(TransportService transportService, ActionFilters actionFilters) { + super(ShareResourceAction.NAME, transportService, actionFilters, ShareResourceRequest::new); } @Override protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { - try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + try { shareResource(request); listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); } catch (Exception e) { From a671cc16a7ff47b351509c2cd31c86ac386cb264 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 14 Oct 2024 17:12:55 -0400 Subject: [PATCH 014/122] Fixes some names and method implementations Signed-off-by: Darshit Chanpura --- .../org/opensearch/sample/SampleResourcePlugin.java | 9 ++++++++- .../org/opensearch/sample/SampleResourceScope.java | 2 +- .../sample/actions/share/ShareResourceRestAction.java | 2 +- .../transport/CreateResourceTransportAction.java | 11 ++++++++++- .../services/org.opensearch.plugins.ResourcePlugin | 1 + 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index abc9ed4de7..a96a3d52ff 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -46,7 +46,9 @@ import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction; import org.opensearch.sample.actions.share.ShareResourceAction; +import org.opensearch.sample.actions.share.ShareResourceRestAction; import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; +import org.opensearch.sample.actions.verify.VerifyResourceAccessRestAction; import org.opensearch.sample.transport.CreateResourceTransportAction; import org.opensearch.sample.transport.ListAccessibleResourcesTransportAction; import org.opensearch.sample.transport.ShareResourceTransportAction; @@ -98,7 +100,12 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - return List.of(new CreateResourceRestAction(), new ListAccessibleResourcesRestAction()); + return List.of( + new CreateResourceRestAction(), + new ListAccessibleResourcesRestAction(), + new VerifyResourceAccessRestAction(), + new ShareResourceRestAction() + ); } @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java index 2784de45b7..90df0d3764 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java @@ -17,7 +17,7 @@ * This class demonstrates a sample implementation of Basic Access Scopes to fit each plugin's use-case. * The plugin then uses this scope when seeking access evaluation for a user on a particular resource. */ -enum SampleResourceScope implements ResourceAccessScope { +public enum SampleResourceScope implements ResourceAccessScope { SAMPLE_FULL_ACCESS("sample_full_access"); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java index 87bc083f2e..347fb49e68 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java @@ -33,7 +33,7 @@ public List routes() { @Override public String getName() { - return "list_sample_resources"; + return "share_sample_resources"; } @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index 985d80b919..2de452a5de 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -17,6 +17,7 @@ import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.ActionFilters; @@ -29,6 +30,7 @@ import org.opensearch.core.xcontent.ToXContent; import org.opensearch.sample.Resource; import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.SampleResourceScope; import org.opensearch.sample.actions.create.CreateResourceAction; import org.opensearch.sample.actions.create.CreateResourceRequest; import org.opensearch.sample.actions.create.CreateResourceResponse; @@ -60,6 +62,7 @@ protected void doExecute(Task task, CreateResourceRequest request, ActionListene createResource(request, listener); listener.onResponse(new CreateResourceResponse("Resource " + request.getResource() + " created successfully.")); } catch (Exception e) { + log.info("Failed to create resource", e); listener.onFailure(e); } } @@ -82,7 +85,13 @@ private void createResource(CreateResourceRequest request, ActionListener getIndexResponseActionListener(ActionListener listener) { - ShareWith shareWith = new ShareWith(List.of()); + SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope( + List.of(), + List.of(), + List.of() + ); + SharedWithScope sharedWithScope = new SharedWithScope(SampleResourceScope.SAMPLE_FULL_ACCESS.getName(), sharedWithPerScope); + ShareWith shareWith = new ShareWith(List.of(sharedWithScope)); return ActionListener.wrap(idxResponse -> { log.info("Created resource: {}", idxResponse.toString()); ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); diff --git a/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin b/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin new file mode 100644 index 0000000000..1ca89eaf74 --- /dev/null +++ b/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin @@ -0,0 +1 @@ +org.opensearch.sample.SampleResourcePlugin \ No newline at end of file From 47b73da2bc522c462d9db6c3ad7acbeff0c1caf9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 14 Oct 2024 17:15:13 -0400 Subject: [PATCH 015/122] Adds few concrete method implementations in security plugin Signed-off-by: Darshit Chanpura --- .../CreateResourceTransportAction.java | 6 +- .../security/OpenSearchSecurityPlugin.java | 31 +++- .../resources/ResourceAccessHandler.java | 159 +++++++++++++++--- .../ResourceManagementRepository.java | 16 +- .../ResourceSharingIndexHandler.java | 97 ++++++----- .../ResourceSharingIndexListener.java | 23 ++- 6 files changed, 252 insertions(+), 80 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index 2de452a5de..8bff7b44a3 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -85,11 +85,7 @@ private void createResource(CreateResourceRequest request, ActionListener getIndexResponseActionListener(ActionListener listener) { - SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope( - List.of(), - List.of(), - List.of() - ); + SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(List.of(), List.of(), List.of()); SharedWithScope sharedWithScope = new SharedWithScope(SampleResourceScope.SAMPLE_FULL_ACCESS.getName(), sharedWithPerScope); ShareWith shareWith = new ShareWith(List.of(sharedWithScope)); return ActionListener.wrap(idxResponse -> { diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e7f013d936..e8b2e45cd4 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -69,6 +69,7 @@ import org.opensearch.SpecialPermission; import org.opensearch.Version; import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.ActionRequest; @@ -119,11 +120,13 @@ import org.opensearch.indices.IndicesService; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.ClusterPlugin; +import org.opensearch.plugins.ExtensiblePlugin; import org.opensearch.plugins.ExtensionAwarePlugin; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.plugins.MapperPlugin; import org.opensearch.plugins.Plugin; import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.ResourcePlugin; import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; @@ -179,6 +182,7 @@ import org.opensearch.security.resolver.IndexResolverReplacer; import org.opensearch.security.resources.ResourceAccessHandler; import org.opensearch.security.resources.ResourceManagementRepository; +import org.opensearch.security.resources.ResourceSharingIndexHandler; import org.opensearch.security.resources.ResourceSharingIndexListener; import org.opensearch.security.rest.DashboardsInfoAction; import org.opensearch.security.rest.SecurityConfigUpdateAction; @@ -237,10 +241,11 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin, + IdentityPlugin, + ResourceAccessControlPlugin, // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings ExtensionAwarePlugin, - IdentityPlugin, - ResourceAccessControlPlugin + ExtensiblePlugin // CS-ENFORCE-SINGLE { @@ -845,6 +850,20 @@ public void onQueryPhase(SearchContext searchContext, long tookInNanos) { } } + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions + @Override + public void loadExtensions(ExtensionLoader loader) { + + log.info("Loading resource plugins"); + for (ResourcePlugin resourcePlugin : loader.loadExtensions(ResourcePlugin.class)) { + String resourceIndex = resourcePlugin.getResourceIndex(); + + this.indicesToListen.add(resourceIndex); + log.info("Loaded resource plugin: {}, index: {}", resourcePlugin, resourceIndex); + } + } + // CS-ENFORCE-SINGLE + @Override public List getActionFilters() { List filters = new ArrayList<>(1); @@ -1209,9 +1228,11 @@ public Collection createComponents( e.subscribeForChanges(dcf); } - resourceAccessHandler = new ResourceAccessHandler(threadPool); + final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; + ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler(resourceSharingIndex, localClient, threadPool); + resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); - rmr = ResourceManagementRepository.create(settings, threadPool, localClient); + rmr = ResourceManagementRepository.create(settings, threadPool, localClient, rsIndexHandler); components.add(adminDns); components.add(cr); @@ -2087,6 +2108,7 @@ public void onNodeStarted(DiscoveryNode localNode) { if (!SSLConfig.isSslOnlyMode() && !client && !disabled && !useClusterStateToInitSecurityConfig(settings)) { cr.initOnNodeStart(); } + // create resource sharing index if absent rmr.createResourceSharingIndexIfAbsent(); final Set securityModules = ReflectionHelper.getModulesLoaded(); @@ -2226,6 +2248,7 @@ public static class GuiceHolder implements LifecycleComponent { private static RemoteClusterService remoteClusterService; private static IndicesService indicesService; private static PitService pitService; + private static ResourceService resourceService; // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions private static ExtensionsManager extensionsManager; diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 838785ee7f..32fa077e71 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -11,8 +11,11 @@ package org.opensearch.security.resources; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -21,7 +24,9 @@ import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; @@ -30,67 +35,177 @@ public class ResourceAccessHandler { private static final Logger LOGGER = LogManager.getLogger(ResourceAccessHandler.class); private final ThreadContext threadContext; - - public ResourceAccessHandler(final ThreadPool threadPool) { + private final ResourceSharingIndexHandler resourceSharingIndexHandler; + private final AdminDNs adminDNs; + + public ResourceAccessHandler( + final ThreadPool threadPool, + final ResourceSharingIndexHandler resourceSharingIndexHandler, + AdminDNs adminDns + ) { super(); this.threadContext = threadPool.getThreadContext(); - } - - public Map> listAccessibleResources() { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Listing accessible resource for: {}", user.getName()); - - // TODO add concrete implementation - return Map.of(); + this.resourceSharingIndexHandler = resourceSharingIndexHandler; + this.adminDNs = adminDns; } public List listAccessibleResourcesInPlugin(String systemIndex) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + if (user == null) { + LOGGER.info("Unable to fetch user details "); + return Collections.emptyList(); + } + LOGGER.info("Listing accessible resource within a system index {} for : {}", systemIndex, user.getName()); - // TODO add concrete implementation - return List.of(); + // TODO check if user is admin, if yes all resources should be accessible + if (adminDNs.isAdmin(user)) { + return loadAllResources(systemIndex); + } + + Set result = new HashSet<>(); + + // 0. Own resources + result.addAll(loadOwnResources(systemIndex, user.getName())); + + // 1. By username + result.addAll(loadSharedWithResources(systemIndex, Set.of(user.getName()), "users")); + + // 2. By roles + Set roles = user.getSecurityRoles(); + result.addAll(loadSharedWithResources(systemIndex, roles, "roles")); + + // 3. By backend_roles + Set backendRoles = user.getRoles(); + result.addAll(loadSharedWithResources(systemIndex, backendRoles, "backend_roles")); + + return result.stream().toList(); } public boolean hasPermission(String resourceId, String systemIndexName, String scope) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); - // TODO add concrete implementation + Set userRoles = user.getSecurityRoles(); + Set userBackendRoles = user.getRoles(); + + ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId); + if (document == null) { + LOGGER.warn("Resource {} not found in index {}", resourceId, systemIndexName); + return false; // If the document doesn't exist, no permissions can be granted + } + + if (isSharedWithEveryone(document) + || isOwnerOfResource(document, user.getName()) + || isSharedWithUser(document, user.getName(), scope) + || isSharedWithGroup(document, userRoles, scope) + || isSharedWithGroup(document, userBackendRoles, scope)) { + LOGGER.info("User {} has {} access to {}", user.getName(), scope, resourceId); + return true; + } + + LOGGER.info("User {} does not have {} access to {} ", user.getName(), scope, resourceId); return false; } public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith); + LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith.toString()); - // TODO add concrete implementation - CreatedBy c = new CreatedBy("", null); - return new ResourceSharing(systemIndexName, resourceId, c, shareWith); + // TODO fix this to fetch user-name correctly, need to hydrate user context since context might have been stashed. + // (persistentHeader?) + CreatedBy createdBy = new CreatedBy("", ""); + return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, createdBy, shareWith); } public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess); - // TODO add concrete implementation - return null; + return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess); } public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, systemIndexName, user.getName()); - // TODO add concrete implementation - return false; + ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId); + if (document == null) { + LOGGER.info("Document {} does not exist in index {}", resourceId, systemIndexName); + return false; + } + if (!(adminDNs.isAdmin(user) || isOwnerOfResource(document, user.getName()))) { + LOGGER.info("User {} does not have access to delete the record {} ", user.getName(), resourceId); + return false; + } + return this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, systemIndexName); } public boolean deleteAllResourceSharingRecordsForCurrentUser() { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); - // TODO add concrete implementation + return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName()); + } + + // Helper methods + + private List loadAllResources(String systemIndex) { + return this.resourceSharingIndexHandler.fetchAllDocuments(systemIndex); + } + + private List loadOwnResources(String systemIndex, String username) { + // TODO check if this magic variable can be replaced + return this.resourceSharingIndexHandler.fetchDocumentsByField(systemIndex, "created_by.user", username); + } + + private List loadSharedWithResources(String systemIndex, Set accessWays, String shareWithType) { + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(systemIndex, accessWays, shareWithType); + } + + private boolean isOwnerOfResource(ResourceSharing document, String userName) { + return document.getCreatedBy() != null && document.getCreatedBy().getUser().equals(userName); + } + + private boolean isSharedWithUser(ResourceSharing document, String userName, String scope) { + return checkSharing(document, "users", userName, scope); + } + + private boolean isSharedWithGroup(ResourceSharing document, Set roles, String scope) { + for (String role : roles) { + if (checkSharing(document, "roles", role, scope)) { + return true; + } + } return false; } + private boolean isSharedWithEveryone(ResourceSharing document) { + return document.getShareWith() != null + && document.getShareWith().getSharedWithScopes().stream().anyMatch(sharedWithScope -> sharedWithScope.getScope().equals("*")); + } + + private boolean checkSharing(ResourceSharing document, String sharingType, String identifier, String scope) { + if (document.getShareWith() == null) { + return false; + } + + return document.getShareWith() + .getSharedWithScopes() + .stream() + .filter(sharedWithScope -> sharedWithScope.getScope().equals(scope)) + .findFirst() + .map(sharedWithScope -> { + SharedWithScope.SharedWithPerScope scopePermissions = sharedWithScope.getSharedWithPerScope(); + + return switch (sharingType) { + case "users" -> scopePermissions.getUsers().contains(identifier); + case "roles" -> scopePermissions.getRoles().contains(identifier); + case "backend_roles" -> scopePermissions.getBackendRoles().contains(identifier); + default -> false; + }; + }) + .orElse(false); // Return false if no matching scope is found + } + } diff --git a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java index 7e347a331d..da3678728d 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java +++ b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java @@ -17,7 +17,6 @@ import org.opensearch.client.Client; import org.opensearch.common.settings.Settings; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.support.ConfigConstants; import org.opensearch.threadpool.ThreadPool; public class ResourceManagementRepository { @@ -40,13 +39,14 @@ protected ResourceManagementRepository( this.resourceSharingIndexHandler = resourceSharingIndexHandler; } - public static ResourceManagementRepository create(Settings settings, final ThreadPool threadPool, Client client) { - final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; - return new ResourceManagementRepository( - threadPool, - client, - new ResourceSharingIndexHandler(resourceSharingIndex, settings, client, threadPool) - ); + public static ResourceManagementRepository create( + Settings settings, + final ThreadPool threadPool, + Client client, + ResourceSharingIndexHandler resourceSharingIndexHandler + ) { + + return new ResourceManagementRepository(threadPool, client, resourceSharingIndexHandler); } public void createResourceSharingIndexIfAbsent() { diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index b6f4b02ade..b175ad53d0 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -10,13 +10,16 @@ package org.opensearch.security.resources; import java.io.IOException; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.accesscontrol.resources.CreatedBy; +import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.admin.indices.create.CreateIndexRequest; @@ -25,7 +28,6 @@ import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; -import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.ToXContent; @@ -39,38 +41,20 @@ public class ResourceSharingIndexHandler { private static final Logger LOGGER = LogManager.getLogger(ResourceSharingIndexHandler.class); - private final Settings settings; - private final Client client; private final String resourceSharingIndex; private final ThreadPool threadPool; - public ResourceSharingIndexHandler(final String indexName, final Settings settings, final Client client, ThreadPool threadPool) { + public ResourceSharingIndexHandler(final String indexName, final Client client, ThreadPool threadPool) { this.resourceSharingIndex = indexName; - this.settings = settings; this.client = client; this.threadPool = threadPool; } public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); - public void createIndex(ActionListener listener) { - try (final ThreadContext.StoredContext threadContext = client.threadPool().getThreadContext().stashContext()) { - client.admin() - .indices() - .create( - new CreateIndexRequest(resourceSharingIndex).settings(INDEX_SETTINGS).waitForActiveShards(1), - ActionListener.runBefore(ActionListener.wrap(r -> { - if (r.isAcknowledged()) { - listener.onResponse(true); - } else listener.onFailure(new SecurityException("Couldn't create resource sharing index " + resourceSharingIndex)); - }, listener::onFailure), threadContext::restore) - ); - } - } - public void createResourceSharingIndexIfAbsent(Callable callable) { // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { @@ -91,14 +75,10 @@ public void createResourceSharingIndexIfAbsent(Callable callable) { } } - public boolean indexResourceSharing( - String resourceId, - String resourceIndex, - CreatedBy createdBy, - ShareWith shareWith, - ActionListener listener - ) throws IOException { - createResourceSharingIndexIfAbsent(() -> { + public boolean indexResourceSharing(String resourceId, String resourceIndex, CreatedBy createdBy, ShareWith shareWith) + throws IOException { + + try { ResourceSharing entry = new ResourceSharing(resourceIndex, resourceId, createdBy, shareWith); IndexRequest ir = client.prepareIndex(resourceSharingIndex) @@ -108,17 +88,58 @@ public boolean indexResourceSharing( LOGGER.info("Index Request: {}", ir.toString()); - ActionListener irListener = ActionListener.wrap(idxResponse -> { - LOGGER.info("Created {} entry.", resourceSharingIndex); - listener.onResponse(idxResponse); - }, (failResponse) -> { - LOGGER.error(failResponse.getMessage()); - LOGGER.info("Failed to create {} entry.", resourceSharingIndex); - listener.onFailure(failResponse); - }); + ActionListener irListener = ActionListener.wrap( + idxResponse -> { LOGGER.info("Created {} entry.", resourceSharingIndex); }, + (failResponse) -> { + LOGGER.error(failResponse.getMessage()); + LOGGER.info("Failed to create {} entry.", resourceSharingIndex); + } + ); client.index(ir, irListener); - return null; - }); + } catch (Exception e) { + LOGGER.info("Failed to create {} entry.", resourceSharingIndex, e); + return false; + } return true; } + + public List fetchDocumentsByField(String systemIndex, String field, String value) { + LOGGER.info("Fetching documents from index: {}, where {} = {}", systemIndex, field, value); + + return List.of(); + } + + public List fetchAllDocuments(String systemIndex) { + LOGGER.info("Fetching all documents from index: {}", systemIndex); + return List.of(); + } + + public List fetchDocumentsForAllScopes(String systemIndex, Set accessWays, String shareWithType) { + return List.of(); + } + + public ResourceSharing fetchDocumentById(String systemIndexName, String resourceId) { + return null; + } + + public ResourceSharing updateResourceSharingInfo(String resourceId, String systemIndexName, CreatedBy createdBy, ShareWith shareWith) { + try { + boolean success = indexResourceSharing(resourceId, systemIndexName, createdBy, shareWith); + return success ? new ResourceSharing(resourceId, systemIndexName, createdBy, shareWith) : null; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { + return null; + } + + public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { + return false; + } + + public boolean deleteAllRecordsForUser(String name) { + return false; + } } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 7a2af9f3bd..d6b1180d46 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -8,13 +8,17 @@ package org.opensearch.security.resources; +import java.io.IOException; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.accesscontrol.resources.CreatedBy; import org.opensearch.client.Client; import org.opensearch.core.index.shard.ShardId; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexingOperationListener; +import org.opensearch.security.support.ConfigConstants; import org.opensearch.threadpool.ThreadPool; /** @@ -26,6 +30,7 @@ public class ResourceSharingIndexListener implements IndexingOperationListener { private final static Logger log = LogManager.getLogger(ResourceSharingIndexListener.class); private static final ResourceSharingIndexListener INSTANCE = new ResourceSharingIndexListener(); + private ResourceSharingIndexHandler resourceSharingIndexHandler; private boolean initialized; @@ -52,6 +57,12 @@ public void initialize(ThreadPool threadPool, Client client) { this.threadPool = threadPool; this.client = client; + this.resourceSharingIndexHandler = new ResourceSharingIndexHandler( + ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, + client, + threadPool + ); + ; } @@ -60,19 +71,25 @@ public boolean isInitialized() { } @Override - public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult result) { // implement a check to see if a resource was updated - log.warn("postIndex called on " + shardId.getIndexName()); + log.info("postIndex called on {}", shardId.getIndexName()); String resourceId = index.id(); String resourceIndex = shardId.getIndexName(); + + try { + this.resourceSharingIndexHandler.indexResourceSharing(resourceId, resourceIndex, new CreatedBy("bleh", ""), null); + log.info("successfully indexed resource {}", resourceId); + } catch (IOException e) { + log.info("failed to index resource {}", resourceId); + throw new RuntimeException(e); + } } @Override - public void postDelete(ShardId shardId, Engine.Delete delete, Engine.DeleteResult result) { // implement a check to see if a resource was deleted From 8942a800623449ebb626cda5a58e5e9bd1791c21 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 15 Oct 2024 01:09:57 -0400 Subject: [PATCH 016/122] Adds capability to introduce index listeners for all resource plugins Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 37 ++++++++++--------- .../transport/SecurityInterceptorTests.java | 4 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e8b2e45cd4..96aa7c2bf6 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -120,7 +120,6 @@ import org.opensearch.indices.IndicesService; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.ClusterPlugin; -import org.opensearch.plugins.ExtensiblePlugin; import org.opensearch.plugins.ExtensionAwarePlugin; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.plugins.MapperPlugin; @@ -244,8 +243,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin IdentityPlugin, ResourceAccessControlPlugin, // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings - ExtensionAwarePlugin, - ExtensiblePlugin + ExtensionAwarePlugin // CS-ENFORCE-SINGLE { @@ -726,6 +724,7 @@ public void onIndexModule(IndexModule indexModule) { ) ); + log.info("Indices to listen to: {}", this.indicesToListen); if (this.indicesToListen.contains(indexModule.getIndex().getName())) { indexModule.addIndexOperationListener(ResourceSharingIndexListener.getInstance()); log.warn("Security plugin started listening to operations on index {}", indexModule.getIndex().getName()); @@ -850,20 +849,6 @@ public void onQueryPhase(SearchContext searchContext, long tookInNanos) { } } - // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions - @Override - public void loadExtensions(ExtensionLoader loader) { - - log.info("Loading resource plugins"); - for (ResourcePlugin resourcePlugin : loader.loadExtensions(ResourcePlugin.class)) { - String resourceIndex = resourcePlugin.getResourceIndex(); - - this.indicesToListen.add(resourceIndex); - log.info("Loaded resource plugin: {}, index: {}", resourcePlugin, resourceIndex); - } - } - // CS-ENFORCE-SINGLE - @Override public List getActionFilters() { List filters = new ArrayList<>(1); @@ -2111,6 +2096,15 @@ public void onNodeStarted(DiscoveryNode localNode) { // create resource sharing index if absent rmr.createResourceSharingIndexIfAbsent(); + + log.info("Loading resource plugins"); + for (ResourcePlugin resourcePlugin : OpenSearchSecurityPlugin.GuiceHolder.getResourceService().listResourcePlugins()) { + String resourceIndex = resourcePlugin.getResourceIndex(); + + this.indicesToListen.add(resourceIndex); + log.info("Loaded resource plugin: {}, index: {}", resourcePlugin, resourceIndex); + } + final Set securityModules = ReflectionHelper.getModulesLoaded(); log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); } @@ -2128,6 +2122,7 @@ public Collection> getGuiceServiceClasses() final List> services = new ArrayList<>(1); services.add(GuiceHolder.class); + log.info("Guice service classes loaded"); return services; } @@ -2259,13 +2254,15 @@ public GuiceHolder( final TransportService remoteClusterService, IndicesService indicesService, PitService pitService, - ExtensionsManager extensionsManager + ExtensionsManager extensionsManager, + ResourceService resourceService ) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; GuiceHolder.pitService = pitService; GuiceHolder.extensionsManager = extensionsManager; + GuiceHolder.resourceService = resourceService; } // CS-ENFORCE-SINGLE @@ -2291,6 +2288,10 @@ public static ExtensionsManager getExtensionsManager() { } // CS-ENFORCE-SINGLE + public static ResourceService getResourceService() { + return resourceService; + } + @Override public void close() {} diff --git a/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java b/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java index d12fafb247..0f7d5c59c5 100644 --- a/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java +++ b/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.opensearch.Version; +import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.action.search.PitService; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.node.DiscoveryNode; @@ -171,7 +172,8 @@ public void setup() { transportService, mock(IndicesService.class), mock(PitService.class), - mock(ExtensionsManager.class) + mock(ExtensionsManager.class), + mock(ResourceService.class) ); // CS-ENFORCE-SINGLE From 2b06603da9d2742efa2c8d984fac044984a246ba Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Nov 2024 13:08:18 -0500 Subject: [PATCH 017/122] Removes sampleplugin to be added in a separate PR Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 166 ---------------- .../java/org/opensearch/sample/Resource.java | 19 -- .../sample/SampleResourcePlugin.java | 178 ------------------ .../sample/SampleResourceScope.java | 33 ---- .../actions/create/CreateResourceAction.java | 29 --- .../actions/create/CreateResourceRequest.java | 50 ----- .../create/CreateResourceResponse.java | 55 ------ .../create/CreateResourceRestAction.java | 55 ------ .../sample/actions/create/SampleResource.java | 56 ------ .../list/ListAccessibleResourcesAction.java | 29 --- .../list/ListAccessibleResourcesRequest.java | 39 ---- .../list/ListAccessibleResourcesResponse.java | 46 ----- .../ListAccessibleResourcesRestAction.java | 44 ----- .../actions/share/ShareResourceAction.java | 26 --- .../actions/share/ShareResourceRequest.java | 52 ----- .../actions/share/ShareResourceResponse.java | 52 ----- .../share/ShareResourceRestAction.java | 51 ----- .../verify/VerifyResourceAccessAction.java | 25 --- .../verify/VerifyResourceAccessRequest.java | 69 ------- .../verify/VerifyResourceAccessResponse.java | 52 ----- .../VerifyResourceAccessRestAction.java | 52 ----- .../CreateResourceTransportAction.java | 99 ---------- ...istAccessibleResourcesTransportAction.java | 56 ------ .../ShareResourceTransportAction.java | 65 ------- .../VerifyResourceAccessTransportAction.java | 58 ------ .../plugin-metadata/plugin-security.policy | 3 - .../org.opensearch.plugins.ResourcePlugin | 1 - .../test/resources/security/esnode-key.pem | 28 --- .../src/test/resources/security/esnode.pem | 25 --- .../src/test/resources/security/kirk-key.pem | 28 --- .../src/test/resources/security/kirk.pem | 27 --- .../src/test/resources/security/root-ca.pem | 28 --- .../src/test/resources/security/sample.pem | 25 --- .../src/test/resources/security/test-kirk.jks | Bin 3766 -> 0 bytes 34 files changed, 1621 deletions(-) delete mode 100644 sample-resource-plugin/build.gradle delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java delete mode 100644 sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy delete mode 100644 sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin delete mode 100644 sample-resource-plugin/src/test/resources/security/esnode-key.pem delete mode 100644 sample-resource-plugin/src/test/resources/security/esnode.pem delete mode 100644 sample-resource-plugin/src/test/resources/security/kirk-key.pem delete mode 100644 sample-resource-plugin/src/test/resources/security/kirk.pem delete mode 100644 sample-resource-plugin/src/test/resources/security/root-ca.pem delete mode 100644 sample-resource-plugin/src/test/resources/security/sample.pem delete mode 100644 sample-resource-plugin/src/test/resources/security/test-kirk.jks diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle deleted file mode 100644 index e9822c1f22..0000000000 --- a/sample-resource-plugin/build.gradle +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -apply plugin: 'opensearch.opensearchplugin' -apply plugin: 'opensearch.testclusters' -apply plugin: 'opensearch.java-rest-test' - -import org.opensearch.gradle.test.RestIntegTestTask - - -opensearchplugin { - name 'opensearch-sample-resource-plugin' - description 'Sample plugin that extends OpenSearch Resource Plugin' - classname 'org.opensearch.sample.SampleResourcePlugin' -} - -ext { - projectSubstitutions = [:] - licenseFile = rootProject.file('LICENSE.txt') - noticeFile = rootProject.file('NOTICE.txt') -} - -repositories { - mavenLocal() - mavenCentral() - maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } -} - -dependencies { -} - -def es_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile -es_tmp_dir.mkdirs() - -File repo = file("$buildDir/testclusters/repo") -def _numNodes = findProperty('numNodes') as Integer ?: 1 - -licenseHeaders.enabled = true -validateNebulaPom.enabled = false -testingConventions.enabled = false -loggerUsageCheck.enabled = false - -javaRestTest.dependsOn(rootProject.assemble) -javaRestTest { - systemProperty 'tests.security.manager', 'false' -} -testClusters.javaRestTest { - testDistribution = 'INTEG_TEST' -} - -task integTest(type: RestIntegTestTask) { - description = "Run tests against a cluster" - testClassesDirs = sourceSets.test.output.classesDirs - classpath = sourceSets.test.runtimeClasspath -} -tasks.named("check").configure { dependsOn(integTest) } - -integTest { - if (project.hasProperty('excludeTests')) { - project.properties['excludeTests']?.replaceAll('\\s', '')?.split('[,;]')?.each { - exclude "${it}" - } - } - systemProperty 'tests.security.manager', 'false' - systemProperty 'java.io.tmpdir', es_tmp_dir.absolutePath - - systemProperty "https", System.getProperty("https") - systemProperty "user", System.getProperty("user") - systemProperty "password", System.getProperty("password") - // Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for - // requests. The 'doFirst' delays reading the debug setting on the cluster till execution time. - doFirst { - // Tell the test JVM if the cluster JVM is running under a debugger so that tests can - // use longer timeouts for requests. - def isDebuggingCluster = getDebug() || System.getProperty("test.debug") != null - systemProperty 'cluster.debug', isDebuggingCluster - // Set number of nodes system property to be used in tests - systemProperty 'cluster.number_of_nodes', "${_numNodes}" - // There seems to be an issue when running multi node run or integ tasks with unicast_hosts - // not being written, the waitForAllConditions ensures it's written - getClusters().forEach { cluster -> - cluster.waitForAllConditions() - } - } - - // The -Dcluster.debug option makes the cluster debuggable; this makes the tests debuggable - if (System.getProperty("test.debug") != null) { - jvmArgs '-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=8000' - } - if (System.getProperty("tests.rest.bwcsuite") == null) { - filter { - excludeTestsMatching "org.opensearch.security.sampleextension.bwc.*IT" - } - } -} -project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build')) -Zip bundle = (Zip) project.getTasks().getByName("bundlePlugin"); -Zip rootBundle = (Zip) rootProject.getTasks().getByName("bundlePlugin"); -integTest.dependsOn(bundle) -integTest.getClusters().forEach{c -> { - c.plugin(rootProject.getObjects().fileProperty().value(rootBundle.getArchiveFile())) - c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile())) -}} - -testClusters.integTest { - testDistribution = 'INTEG_TEST' - - // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1 - if (_numNodes > 1) numberOfNodes = _numNodes - // When running integration tests it doesn't forward the --debug-jvm to the cluster anymore - // i.e. we have to use a custom property to flag when we want to debug OpenSearch JVM - // since we also support multi node integration tests we increase debugPort per node - if (System.getProperty("cluster.debug") != null) { - def debugPort = 5005 - nodes.forEach { node -> - node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=*:${debugPort}") - debugPort += 1 - } - } - setting 'path.repo', repo.absolutePath -} - -afterEvaluate { - testClusters.integTest.nodes.each { node -> - def plugins = node.plugins - def firstPlugin = plugins.get(0) - if (firstPlugin.provider == project.bundlePlugin.archiveFile) { - plugins.remove(0) - plugins.add(firstPlugin) - } - - node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem")) - node.extraConfigFile("kirk-key.pem", file("src/test/resources/security/kirk-key.pem")) - node.extraConfigFile("esnode.pem", file("src/test/resources/security/esnode.pem")) - node.extraConfigFile("esnode-key.pem", file("src/test/resources/security/esnode-key.pem")) - node.extraConfigFile("root-ca.pem", file("src/test/resources/security/root-ca.pem")) - node.setting("plugins.security.ssl.transport.pemcert_filepath", "esnode.pem") - node.setting("plugins.security.ssl.transport.pemkey_filepath", "esnode-key.pem") - node.setting("plugins.security.ssl.transport.pemtrustedcas_filepath", "root-ca.pem") - node.setting("plugins.security.ssl.transport.enforce_hostname_verification", "false") - node.setting("plugins.security.ssl.http.enabled", "true") - node.setting("plugins.security.ssl.http.pemcert_filepath", "esnode.pem") - node.setting("plugins.security.ssl.http.pemkey_filepath", "esnode-key.pem") - node.setting("plugins.security.ssl.http.pemtrustedcas_filepath", "root-ca.pem") - node.setting("plugins.security.allow_unsafe_democertificates", "true") - node.setting("plugins.security.allow_default_init_securityindex", "true") - node.setting("plugins.security.authcz.admin_dn", "\n - CN=kirk,OU=client,O=client,L=test,C=de") - node.setting("plugins.security.audit.type", "internal_opensearch") - node.setting("plugins.security.enable_snapshot_restore_privilege", "true") - node.setting("plugins.security.check_snapshot_restore_write_privileges", "true") - node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]") - } -} - -run { - doFirst { - // There seems to be an issue when running multi node run or integ tasks with unicast_hosts - // not being written, the waitForAllConditions ensures it's written - getClusters().forEach { cluster -> - cluster.waitForAllConditions() - } - } - useCluster testClusters.integTest -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java deleted file mode 100644 index 36e74f1624..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.sample; - -import org.opensearch.core.common.io.stream.NamedWriteable; -import org.opensearch.core.xcontent.ToXContentFragment; - -public abstract class Resource implements NamedWriteable, ToXContentFragment { - protected abstract String getResourceIndex(); -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java deleted file mode 100644 index a96a3d52ff..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * 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. - */ -package org.opensearch.sample; - -import java.util.*; -import java.util.function.Supplier; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.action.ActionRequest; -import org.opensearch.client.Client; -import org.opensearch.cluster.metadata.IndexNameExpressionResolver; -import org.opensearch.cluster.node.DiscoveryNodes; -import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.inject.Inject; -import org.opensearch.common.lifecycle.Lifecycle; -import org.opensearch.common.lifecycle.LifecycleComponent; -import org.opensearch.common.lifecycle.LifecycleListener; -import org.opensearch.common.settings.ClusterSettings; -import org.opensearch.common.settings.IndexScopedSettings; -import org.opensearch.common.settings.Settings; -import org.opensearch.common.settings.SettingsFilter; -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.NamedWriteableRegistry; -import org.opensearch.core.xcontent.NamedXContentRegistry; -import org.opensearch.env.Environment; -import org.opensearch.env.NodeEnvironment; -import org.opensearch.indices.SystemIndexDescriptor; -import org.opensearch.plugins.ActionPlugin; -import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.ResourcePlugin; -import org.opensearch.plugins.SystemIndexPlugin; -import org.opensearch.repositories.RepositoriesService; -import org.opensearch.rest.RestController; -import org.opensearch.rest.RestHandler; -import org.opensearch.sample.actions.create.CreateResourceAction; -import org.opensearch.sample.actions.create.CreateResourceRestAction; -import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; -import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction; -import org.opensearch.sample.actions.share.ShareResourceAction; -import org.opensearch.sample.actions.share.ShareResourceRestAction; -import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; -import org.opensearch.sample.actions.verify.VerifyResourceAccessRestAction; -import org.opensearch.sample.transport.CreateResourceTransportAction; -import org.opensearch.sample.transport.ListAccessibleResourcesTransportAction; -import org.opensearch.sample.transport.ShareResourceTransportAction; -import org.opensearch.sample.transport.VerifyResourceAccessTransportAction; -import org.opensearch.script.ScriptService; -import org.opensearch.threadpool.ThreadPool; -import org.opensearch.watcher.ResourceWatcherService; - -/** - * Sample Resource plugin. - * It uses ".sample_resources" index to manage its resources, and exposes a REST API - * - */ -public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { - private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); - - public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; - - public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); - - private Client client; - - @Override - public Collection createComponents( - Client client, - ClusterService clusterService, - ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, - ScriptService scriptService, - NamedXContentRegistry xContentRegistry, - Environment environment, - NodeEnvironment nodeEnvironment, - NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier repositoriesServiceSupplier - ) { - this.client = client; - log.info("Loaded SampleResourcePlugin components."); - return Collections.emptyList(); - } - - @Override - public List getRestHandlers( - Settings settings, - RestController restController, - ClusterSettings clusterSettings, - IndexScopedSettings indexScopedSettings, - SettingsFilter settingsFilter, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier nodesInCluster - ) { - return List.of( - new CreateResourceRestAction(), - new ListAccessibleResourcesRestAction(), - new VerifyResourceAccessRestAction(), - new ShareResourceRestAction() - ); - } - - @Override - public List> getActions() { - return List.of( - new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class), - new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class), - new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class), - new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class) - ); - } - - @Override - public Collection getSystemIndexDescriptors(Settings settings) { - final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Example index with resources"); - return Collections.singletonList(systemIndexDescriptor); - } - - @Override - public String getResourceType() { - return ""; - } - - @Override - public String getResourceIndex() { - return RESOURCE_INDEX_NAME; - } - - @Override - public Collection> getGuiceServiceClasses() { - final List> services = new ArrayList<>(1); - services.add(GuiceHolder.class); - return services; - } - - public static class GuiceHolder implements LifecycleComponent { - - private static ResourceService resourceService; - - @Inject - public GuiceHolder(final ResourceService resourceService) { - GuiceHolder.resourceService = resourceService; - } - - public static ResourceService getResourceService() { - return resourceService; - } - - @Override - public void close() {} - - @Override - public Lifecycle.State lifecycleState() { - return null; - } - - @Override - public void addLifecycleListener(LifecycleListener listener) {} - - @Override - public void removeLifecycleListener(LifecycleListener listener) {} - - @Override - public void start() {} - - @Override - public void stop() {} - - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java deleted file mode 100644 index 90df0d3764..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.sample; - -import org.opensearch.accesscontrol.resources.ResourceAccessScope; - -/** - * This class demonstrates a sample implementation of Basic Access Scopes to fit each plugin's use-case. - * The plugin then uses this scope when seeking access evaluation for a user on a particular resource. - */ -public enum SampleResourceScope implements ResourceAccessScope { - - SAMPLE_FULL_ACCESS("sample_full_access"); - - private final String name; - - SampleResourceScope(String scopeName) { - this.name = scopeName; - } - - public String getName() { - return name; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java deleted file mode 100644 index e7c02278ab..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.create; - -import org.opensearch.action.ActionType; - -/** - * Action to create a sample resource - */ -public class CreateResourceAction extends ActionType { - /** - * Create sample resource action instance - */ - public static final CreateResourceAction INSTANCE = new CreateResourceAction(); - /** - * Create sample resource action name - */ - public static final String NAME = "cluster:admin/sample-resource-plugin/create"; - - private CreateResourceAction() { - super(NAME, CreateResourceResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java deleted file mode 100644 index b31a4b7f2b..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.create; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.Resource; - -/** - * Request object for CreateSampleResource transport action - */ -public class CreateResourceRequest extends ActionRequest { - - private final Resource resource; - - /** - * Default constructor - */ - public CreateResourceRequest(Resource resource) { - this.resource = resource; - } - - public CreateResourceRequest(StreamInput in) throws IOException { - this.resource = in.readNamedWriteable(Resource.class); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - resource.writeTo(out); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public Resource getResource() { - return this.resource; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java deleted file mode 100644 index 6b966ed08d..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.create; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -/** - * Response to a CreateSampleResourceRequest - */ -public class CreateResourceResponse extends ActionResponse implements ToXContentObject { - private final String message; - - /** - * Default constructor - * - * @param message The message - */ - public CreateResourceResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - /** - * Constructor with StreamInput - * - * @param in the stream input - */ - public CreateResourceResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java deleted file mode 100644 index 86346cc279..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.create; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.POST; - -public class CreateResourceRestAction extends BaseRestHandler { - - public CreateResourceRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/resource")); - } - - @Override - public String getName() { - return "create_sample_resource"; - } - - @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String name = (String) source.get("name"); - SampleResource resource = new SampleResource(); - resource.setName(name); - final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest(resource); - return channel -> client.executeLocally( - CreateResourceAction.INSTANCE, - createSampleResourceRequest, - new RestToXContentListener<>(channel) - ); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java deleted file mode 100644 index 1566abfe69..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.sample.actions.create; - -import java.io.IOException; - -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.sample.Resource; - -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; - -public class SampleResource extends Resource { - - private String name; - - public SampleResource() {} - - SampleResource(StreamInput in) throws IOException { - this.name = in.readString(); - } - - @Override - public String getResourceIndex() { - return RESOURCE_INDEX_NAME; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("name", name).endObject(); - } - - @Override - public void writeTo(StreamOutput streamOutput) throws IOException { - streamOutput.writeString(name); - } - - @Override - public String getWriteableName() { - return "sample_resource"; - } - - public void setName(String name) { - this.name = name; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java deleted file mode 100644 index b4e9e29e22..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.list; - -import org.opensearch.action.ActionType; - -/** - * Action to list sample resources - */ -public class ListAccessibleResourcesAction extends ActionType { - /** - * List sample resource action instance - */ - public static final ListAccessibleResourcesAction INSTANCE = new ListAccessibleResourcesAction(); - /** - * List sample resource action name - */ - public static final String NAME = "cluster:admin/sample-resource-plugin/list"; - - private ListAccessibleResourcesAction() { - super(NAME, ListAccessibleResourcesResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java deleted file mode 100644 index b4c0961774..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.list; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; - -/** - * Request object for ListSampleResource transport action - */ -public class ListAccessibleResourcesRequest extends ActionRequest { - - public ListAccessibleResourcesRequest() {} - - /** - * Constructor with stream input - * @param in the stream input - * @throws IOException IOException - */ - public ListAccessibleResourcesRequest(final StreamInput in) throws IOException {} - - @Override - public void writeTo(final StreamOutput out) throws IOException {} - - @Override - public ActionRequestValidationException validate() { - return null; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java deleted file mode 100644 index 47a8f88e4e..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.list; - -import java.io.IOException; -import java.util.List; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -/** - * Response to a ListAccessibleResourcesRequest - */ -public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { - private final List resourceIds; - - public ListAccessibleResourcesResponse(List resourceIds) { - this.resourceIds = resourceIds; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeStringArray(resourceIds.toArray(new String[0])); - } - - public ListAccessibleResourcesResponse(final StreamInput in) throws IOException { - resourceIds = in.readStringList(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("resource-ids", resourceIds); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java deleted file mode 100644 index bb921fce00..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.list; - -import java.util.List; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.GET; - -public class ListAccessibleResourcesRestAction extends BaseRestHandler { - - public ListAccessibleResourcesRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/resource")); - } - - @Override - public String getName() { - return "list_sample_resources"; - } - - @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { - final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(); - return channel -> client.executeLocally( - ListAccessibleResourcesAction.INSTANCE, - listAccessibleResourcesRequest, - new RestToXContentListener<>(channel) - ); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java deleted file mode 100644 index d362b1927c..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.share; - -import org.opensearch.action.ActionType; - -public class ShareResourceAction extends ActionType { - /** - * List sample resource action instance - */ - public static final ShareResourceAction INSTANCE = new ShareResourceAction(); - /** - * List sample resource action name - */ - public static final String NAME = "cluster:admin/sample-resource-plugin/share"; - - private ShareResourceAction() { - super(NAME, ShareResourceResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java deleted file mode 100644 index 01866fd516..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.share; - -import java.io.IOException; - -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; - -public class ShareResourceRequest extends ActionRequest { - - private final String resourceId; - private final ShareWith shareWith; - - public ShareResourceRequest(String resourceId, ShareWith shareWith) { - this.resourceId = resourceId; - this.shareWith = shareWith; - } - - public ShareResourceRequest(StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.shareWith = in.readNamedWriteable(ShareWith.class); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeNamedWriteable(shareWith); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public String getResourceId() { - return resourceId; - } - - public ShareWith getShareWith() { - return shareWith; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java deleted file mode 100644 index a6a85d206d..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.share; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class ShareResourceResponse extends ActionResponse implements ToXContentObject { - private final String message; - - /** - * Default constructor - * - * @param message The message - */ - public ShareResourceResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - /** - * Constructor with StreamInput - * - * @param in the stream input - */ - public ShareResourceResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java deleted file mode 100644 index 347fb49e68..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.share; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.GET; - -public class ShareResourceRestAction extends BaseRestHandler { - - public ShareResourceRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/share/{resource_id}")); - } - - @Override - public String getName() { - return "share_sample_resources"; - } - - @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceId = (String) source.get("resource_id"); - ShareWith shareWith = (ShareWith) source.get("share_with"); - final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, shareWith); - return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java deleted file mode 100644 index 1378d561f5..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.verify; - -import org.opensearch.action.ActionType; - -/** - * Action to verify resource access for current user - */ -public class VerifyResourceAccessAction extends ActionType { - - public static final VerifyResourceAccessAction INSTANCE = new VerifyResourceAccessAction(); - - public static final String NAME = "cluster:admin/sample-resource-plugin/verify/resource_access"; - - private VerifyResourceAccessAction() { - super(NAME, VerifyResourceAccessResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java deleted file mode 100644 index e9b20118db..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.verify; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; - -public class VerifyResourceAccessRequest extends ActionRequest { - - private final String resourceId; - - private final String sourceIdx; - - private final String scope; - - /** - * Default constructor - */ - public VerifyResourceAccessRequest(String resourceId, String sourceIdx, String scope) { - this.resourceId = resourceId; - this.sourceIdx = sourceIdx; - this.scope = scope; - } - - /** - * Constructor with stream input - * @param in the stream input - * @throws IOException IOException - */ - public VerifyResourceAccessRequest(final StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.sourceIdx = in.readString(); - this.scope = in.readString(); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeString(sourceIdx); - out.writeString(scope); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public String getResourceId() { - return resourceId; - } - - public String getSourceIdx() { - return sourceIdx; - } - - public String getScope() { - return scope; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java deleted file mode 100644 index 660ac03f71..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.verify; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class VerifyResourceAccessResponse extends ActionResponse implements ToXContentObject { - private final String message; - - /** - * Default constructor - * - * @param message The message - */ - public VerifyResourceAccessResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - /** - * Constructor with StreamInput - * - * @param in the stream input - */ - public VerifyResourceAccessResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java deleted file mode 100644 index 34bfed4e9f..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.verify; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.POST; - -public class VerifyResourceAccessRestAction extends BaseRestHandler { - - public VerifyResourceAccessRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/verify_resource_access")); - } - - @Override - public String getName() { - return "verify_resource_access"; - } - - @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceIdx = (String) source.get("resource_idx"); - String sourceIdx = (String) source.get("source_idx"); - String scope = (String) source.get("scope"); - - // final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest<>(resource); - return channel -> client.executeLocally(VerifyResourceAccessAction.INSTANCE, null, new RestToXContentListener<>(channel)); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java deleted file mode 100644 index 8bff7b44a3..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport; - -import java.io.IOException; -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.accesscontrol.resources.SharedWithScope; -import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.index.IndexResponse; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.action.support.WriteRequest; -import org.opensearch.client.Client; -import org.opensearch.common.inject.Inject; -import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.core.action.ActionListener; -import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.sample.Resource; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.SampleResourceScope; -import org.opensearch.sample.actions.create.CreateResourceAction; -import org.opensearch.sample.actions.create.CreateResourceRequest; -import org.opensearch.sample.actions.create.CreateResourceResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; - -/** - * Transport action for CreateSampleResource. - */ -public class CreateResourceTransportAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(CreateResourceTransportAction.class); - - private final TransportService transportService; - private final Client nodeClient; - - @Inject - public CreateResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { - super(CreateResourceAction.NAME, transportService, actionFilters, CreateResourceRequest::new); - this.transportService = transportService; - this.nodeClient = nodeClient; - } - - @Override - protected void doExecute(Task task, CreateResourceRequest request, ActionListener listener) { - try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { - createResource(request, listener); - listener.onResponse(new CreateResourceResponse("Resource " + request.getResource() + " created successfully.")); - } catch (Exception e) { - log.info("Failed to create resource", e); - listener.onFailure(e); - } - } - - private void createResource(CreateResourceRequest request, ActionListener listener) { - Resource sample = request.getResource(); - try { - IndexRequest ir = nodeClient.prepareIndex(RESOURCE_INDEX_NAME) - .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .setSource(sample.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) - .request(); - - log.warn("Index Request: {}", ir.toString()); - - ActionListener irListener = getIndexResponseActionListener(listener); - nodeClient.index(ir, irListener); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static ActionListener getIndexResponseActionListener(ActionListener listener) { - SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(List.of(), List.of(), List.of()); - SharedWithScope sharedWithScope = new SharedWithScope(SampleResourceScope.SAMPLE_FULL_ACCESS.getName(), sharedWithPerScope); - ShareWith shareWith = new ShareWith(List.of(sharedWithScope)); - return ActionListener.wrap(idxResponse -> { - log.info("Created resource: {}", idxResponse.toString()); - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - ResourceSharing sharing = rs.getResourceAccessControlPlugin().shareWith(idxResponse.getId(), idxResponse.getIndex(), shareWith); - log.info("Created resource sharing entry: {}", sharing.toString()); - }, listener::onFailure); - } - -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java deleted file mode 100644 index d56eb6d291..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport; - -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; -import org.opensearch.sample.actions.list.ListAccessibleResourcesRequest; -import org.opensearch.sample.actions.list.ListAccessibleResourcesResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; - -/** - * Transport action for ListSampleResource. - */ -public class ListAccessibleResourcesTransportAction extends HandledTransportAction< - ListAccessibleResourcesRequest, - ListAccessibleResourcesResponse> { - private static final Logger log = LogManager.getLogger(ListAccessibleResourcesTransportAction.class); - - @Inject - public ListAccessibleResourcesTransportAction(TransportService transportService, ActionFilters actionFilters) { - super(ListAccessibleResourcesAction.NAME, transportService, actionFilters, ListAccessibleResourcesRequest::new); - } - - @Override - protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { - try { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); - log.info("Successfully fetched accessible resources for current user"); - listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); - } catch (Exception e) { - log.info("Failed to list accessible resources for current user: ", e); - listener.onFailure(e); - } - - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java deleted file mode 100644 index ccbfc31b78..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport; - -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.share.ShareResourceAction; -import org.opensearch.sample.actions.share.ShareResourceRequest; -import org.opensearch.sample.actions.share.ShareResourceResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; - -/** - * Transport action for CreateSampleResource. - */ -public class ShareResourceTransportAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); - - @Inject - public ShareResourceTransportAction(TransportService transportService, ActionFilters actionFilters) { - super(ShareResourceAction.NAME, transportService, actionFilters, ShareResourceRequest::new); - } - - @Override - protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { - try { - shareResource(request); - listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); - } catch (Exception e) { - listener.onFailure(e); - } - } - - private void shareResource(ShareResourceRequest request) { - try { - ShareWith shareWith = new ShareWith(List.of()); - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - ResourceSharing sharing = rs.getResourceAccessControlPlugin() - .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, shareWith); - log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); - } catch (Exception e) { - log.info("Failed to share resource {}", request.getResourceId(), e); - throw e; - } - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java deleted file mode 100644 index 947dcec59e..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.client.Client; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; -import org.opensearch.sample.actions.verify.VerifyResourceAccessRequest; -import org.opensearch.sample.actions.verify.VerifyResourceAccessResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -public class VerifyResourceAccessTransportAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class); - - @Inject - public VerifyResourceAccessTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { - super(VerifyResourceAccessAction.NAME, transportService, actionFilters, VerifyResourceAccessRequest::new); - } - - @Override - protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) { - try { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - boolean hasRequestedScopeAccess = rs.getResourceAccessControlPlugin() - .hasPermission(request.getResourceId(), request.getSourceIdx(), request.getScope()); - - StringBuilder sb = new StringBuilder(); - sb.append("User does"); - sb.append(hasRequestedScopeAccess ? " " : " not "); - sb.append("have requested scope "); - sb.append(request.getScope()); - sb.append(" access to "); - sb.append(request.getResourceId()); - - log.info(sb.toString()); - listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); - } catch (Exception e) { - log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); - listener.onFailure(e); - } - } - -} diff --git a/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy b/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy deleted file mode 100644 index a5dfc33a87..0000000000 --- a/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy +++ /dev/null @@ -1,3 +0,0 @@ -grant { - permission java.lang.RuntimePermission "getClassLoader"; -}; \ No newline at end of file diff --git a/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin b/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin deleted file mode 100644 index 1ca89eaf74..0000000000 --- a/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin +++ /dev/null @@ -1 +0,0 @@ -org.opensearch.sample.SampleResourcePlugin \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/esnode-key.pem b/sample-resource-plugin/src/test/resources/security/esnode-key.pem deleted file mode 100644 index e90562be43..0000000000 --- a/sample-resource-plugin/src/test/resources/security/esnode-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCm93kXteDQHMAv -bUPNPW5pyRHKDD42XGWSgq0k1D29C/UdyL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0 -o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0HGkn47XVu3EwbfrTENg3jFu+Oem6a/50 -1SzITzJWtS0cn2dIFOBimTVpT/4Zv5qrXA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1 -MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8ndibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b -6l+KLo3IKpfTbAIJXIO+M67FLtWKtttDao94B069skzKk6FPgW/OZh6PRCD0oxOa -vV+ld2SjAgMBAAECggEAQK1+uAOZeaSZggW2jQut+MaN4JHLi61RH2cFgU3COLgo -FIiNjFn8f2KKU3gpkt1It8PjlmprpYut4wHI7r6UQfuv7ZrmncRiPWHm9PB82+ZQ -5MXYqj4YUxoQJ62Cyz4sM6BobZDrjG6HHGTzuwiKvHHkbsEE9jQ4E5m7yfbVvM0O -zvwrSOM1tkZihKSTpR0j2+taji914tjBssbn12TMZQL5ItGnhR3luY8mEwT9MNkZ -xg0VcREoAH+pu9FE0vPUgLVzhJ3be7qZTTSRqv08bmW+y1plu80GbppePcgYhEow -dlW4l6XPJaHVSn1lSFHE6QAx6sqiAnBz0NoTPIaLyQKBgQDZqDOlhCRciMRicSXn -7yid9rhEmdMkySJHTVFOidFWwlBcp0fGxxn8UNSBcXdSy7GLlUtH41W9PWl8tp9U -hQiiXORxOJ7ZcB80uNKXF01hpPj2DpFPWyHFxpDkWiTAYpZl68rOlYujxZUjJIej -VvcykBC2BlEOG9uZv2kxcqLyJwKBgQDEYULTxaTuLIa17wU3nAhaainKB3vHxw9B -Ksy5p3ND43UNEKkQm7K/WENx0q47TA1mKD9i+BhaLod98mu0YZ+BCUNgWKcBHK8c -uXpauvM/pLhFLXZ2jvEJVpFY3J79FSRK8bwE9RgKfVKMMgEk4zOyZowS8WScOqiy -hnQn1vKTJQKBgElhYuAnl9a2qXcC7KOwRsJS3rcKIVxijzL4xzOyVShp5IwIPbOv -hnxBiBOH/JGmaNpFYBcBdvORE9JfA4KMQ2fx53agfzWRjoPI1/7mdUk5RFI4gRb/ -A3jZRBoopgFSe6ArCbnyQxzYzToG48/Wzwp19ZxYrtUR4UyJct6f5n27AoGBAJDh -KIpQQDOvCdtjcbfrF4aM2DPCfaGPzENJriwxy6oEPzDaX8Bu/dqI5Ykt43i/zQrX -GpyLaHvv4+oZVTiI5UIvcVO9U8hQPyiz9f7F+fu0LHZs6f7hyhYXlbe3XFxeop3f -5dTKdWgXuTTRF2L9dABkA2deS9mutRKwezWBMQk5AoGBALPtX0FrT1zIosibmlud -tu49A/0KZu4PBjrFMYTSEWGNJez3Fb2VsJwylVl6HivwbP61FhlYfyksCzQQFU71 -+x7Nmybp7PmpEBECr3deoZKQ/acNHn0iwb0It+YqV5+TquQebqgwK6WCLsMuiYKT -bg/ch9Rhxbq22yrVgWHh6epp ------END PRIVATE KEY----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/esnode.pem b/sample-resource-plugin/src/test/resources/security/esnode.pem deleted file mode 100644 index 44101f0b37..0000000000 --- a/sample-resource-plugin/src/test/resources/security/esnode.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v -dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT -AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl -MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud -yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 -HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr -XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n -dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD -ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R -BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA -AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF -BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo -wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ -KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz -pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi -7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh -hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L -camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg -PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg= ------END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/kirk-key.pem b/sample-resource-plugin/src/test/resources/security/kirk-key.pem deleted file mode 100644 index 1949c26139..0000000000 --- a/sample-resource-plugin/src/test/resources/security/kirk-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCVXDgEJQorgfXp -gpY0TgF55bD2xuzxN5Dc9rDfgWxrsOvOloMpd7k6FR71bKWjJi1KptSmM/cDElky -AWYKSfYWGiGxsQ+EQW+6kwCfEOHXQldn+0+JcWqP+osSPjtJfwRvRN5kRqP69MPo -7U0N2kdqenqMWjmG1chDGLRSOEGU5HIBiDxsZtOcvMaJ8b1eaW0lvS+6gFQ80AvB -GBkDDCOHHLtDXBylrZk2CQP8AzxNicIZ4B8G3CG3OHA8+nBtEtxZoIihrrkqlMt+ -b/5N8u8zB0Encew0kdrc4R/2wS//ahr6U+9Siq8T7WsUtGwKj3BJClg6OyDJRhlu -y2gFnxoPAgMBAAECggEAP5TOycDkx+megAWVoHV2fmgvgZXkBrlzQwUG/VZQi7V4 -ZGzBMBVltdqI38wc5MtbK3TCgHANnnKgor9iq02Z4wXDwytPIiti/ycV9CDRKvv0 -TnD2hllQFjN/IUh5n4thHWbRTxmdM7cfcNgX3aZGkYbLBVVhOMtn4VwyYu/Mxy8j -xClZT2xKOHkxqwmWPmdDTbAeZIbSv7RkIGfrKuQyUGUaWhrPslvYzFkYZ0umaDgQ -OAthZew5Bz3OfUGOMPLH61SVPuJZh9zN1hTWOvT65WFWfsPd2yStI+WD/5PU1Doo -1RyeHJO7s3ug8JPbtNJmaJwHe9nXBb/HXFdqb976yQKBgQDNYhpu+MYSYupaYqjs -9YFmHQNKpNZqgZ4ceRFZ6cMJoqpI5dpEMqToFH7tpor72Lturct2U9nc2WR0HeEs -/6tiptyMPTFEiMFb1opQlXF2ae7LeJllntDGN0Q6vxKnQV+7VMcXA0Y8F7tvGDy3 -qJu5lfvB1mNM2I6y/eMxjBuQhwKBgQC6K41DXMFro0UnoO879pOQYMydCErJRmjG -/tZSy3Wj4KA/QJsDSViwGfvdPuHZRaG9WtxdL6kn0w1exM9Rb0bBKl36lvi7o7xv -M+Lw9eyXMkww8/F5d7YYH77gIhGo+RITkKI3+5BxeBaUnrGvmHrpmpgRXWmINqr0 -0jsnN3u0OQKBgCf45vIgItSjQb8zonLz2SpZjTFy4XQ7I92gxnq8X0Q5z3B+o7tQ -K/4rNwTju/sGFHyXAJlX+nfcK4vZ4OBUJjP+C8CTjEotX4yTNbo3S6zjMyGQqDI5 -9aIOUY4pb+TzeUFJX7If5gR+DfGyQubvvtcg1K3GHu9u2l8FwLj87sRzAoGAflQF -RHuRiG+/AngTPnZAhc0Zq0kwLkpH2Rid6IrFZhGLy8AUL/O6aa0IGoaMDLpSWUJp -nBY2S57MSM11/MVslrEgGmYNnI4r1K25xlaqV6K6ztEJv6n69327MS4NG8L/gCU5 -3pEm38hkUi8pVYU7in7rx4TCkrq94OkzWJYurAkCgYATQCL/rJLQAlJIGulp8s6h -mQGwy8vIqMjAdHGLrCS35sVYBXG13knS52LJHvbVee39AbD5/LlWvjJGlQMzCLrw -F7oILW5kXxhb8S73GWcuMbuQMFVHFONbZAZgn+C9FW4l7XyRdkrbR1MRZ2km8YMs -/AHmo368d4PSNRMMzLHw8Q== ------END PRIVATE KEY----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/kirk.pem b/sample-resource-plugin/src/test/resources/security/kirk.pem deleted file mode 100644 index 36b7e19a75..0000000000 --- a/sample-resource-plugin/src/test/resources/security/kirk.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEmDCCA4CgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLcwDQYJKoZIhvcNAQEL -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v -dCBDQTAeFw0yNDAyMjAxNzA0MjRaFw0zNDAyMTcxNzA0MjRaME0xCzAJBgNVBAYT -AmRlMQ0wCwYDVQQHDAR0ZXN0MQ8wDQYDVQQKDAZjbGllbnQxDzANBgNVBAsMBmNs -aWVudDENMAsGA1UEAwwEa2lyazCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAJVcOAQlCiuB9emCljROAXnlsPbG7PE3kNz2sN+BbGuw686Wgyl3uToVHvVs -paMmLUqm1KYz9wMSWTIBZgpJ9hYaIbGxD4RBb7qTAJ8Q4ddCV2f7T4lxao/6ixI+ -O0l/BG9E3mRGo/r0w+jtTQ3aR2p6eoxaOYbVyEMYtFI4QZTkcgGIPGxm05y8xonx -vV5pbSW9L7qAVDzQC8EYGQMMI4ccu0NcHKWtmTYJA/wDPE2JwhngHwbcIbc4cDz6 -cG0S3FmgiKGuuSqUy35v/k3y7zMHQSdx7DSR2tzhH/bBL/9qGvpT71KKrxPtaxS0 -bAqPcEkKWDo7IMlGGW7LaAWfGg8CAwEAAaOCASswggEnMAwGA1UdEwEB/wQCMAAw -DgYDVR0PAQH/BAQDAgXgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMB0GA1UdDgQW -BBSjMS8tgguX/V7KSGLoGg7K6XMzIDCBzwYDVR0jBIHHMIHEgBQXh9+gWutmEqfV -0Pi6EkU8tysAnKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS -JomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAf -BgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBs -ZSBDb20gSW5jLiBSb290IENBghQNZAmZZn3EFOxBR4630XlhI+mo4jANBgkqhkiG -9w0BAQsFAAOCAQEACEUPPE66/Ot3vZqRGpjDjPHAdtOq+ebaglQhvYcnDw8LOZm8 -Gbh9M88CiO6UxC8ipQLTPh2yyeWArkpJzJK/Pi1eoF1XLiAa0sQ/RaJfQWPm9dvl -1ZQeK5vfD4147b3iBobwEV+CR04SKow0YeEEzAJvzr8YdKI6jqr+2GjjVqzxvRBy -KRVHWCFiR7bZhHGLq3br8hSu0hwjb3oGa1ZI8dui6ujyZt6nm6BoEkau3G/6+zq9 -E6vX3+8Fj4HKCAL6i0SwfGmEpTNp5WUhqibK/fMhhmMT4Mx6MxkT+OFnIjdUU0S/ -e3kgnG8qjficUr38CyEli1U0M7koIXUZI7r+LQ== ------END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/root-ca.pem b/sample-resource-plugin/src/test/resources/security/root-ca.pem deleted file mode 100644 index d33f5f7216..0000000000 --- a/sample-resource-plugin/src/test/resources/security/root-ca.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIExjCCA66gAwIBAgIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcNAQEL -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v -dCBDQTAeFw0yNDAyMjAxNzAwMzZaFw0zNDAyMTcxNzAwMzZaMIGPMRMwEQYKCZIm -iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ -RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 -IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEPyN7J9VGPyJcQmCBl5TGwfSzvVdWwoQU -j9aEsdfFJ6pBCDQSsj8Lv4RqL0dZra7h7SpZLLX/YZcnjikrYC+rP5OwsI9xEE/4 -U98CsTBPhIMgqFK6SzNE5494BsAk4cL72dOOc8tX19oDS/PvBULbNkthQ0aAF1dg -vbrHvu7hq7LisB5ZRGHVE1k/AbCs2PaaKkn2jCw/b+U0Ml9qPuuEgz2mAqJDGYoA -WSR4YXrOcrmPuRqbws464YZbJW898/0Pn/U300ed+4YHiNYLLJp51AMkR4YEw969 -VRPbWIvLrd0PQBooC/eLrL6rvud/GpYhdQEUx8qcNCKd4bz3OaQ5AgMBAAGjggEW -MIIBEjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQU -F4ffoFrrZhKn1dD4uhJFPLcrAJwwgc8GA1UdIwSBxzCBxIAUF4ffoFrrZhKn1dD4 -uhJFPLcrAJyhgZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJ -k/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYD -VQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUg -Q29tIEluYy4gUm9vdCBDQYIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcN -AQELBQADggEBAL3Q3AHUhMiLUy6OlLSt8wX9I2oNGDKbBu0atpUNDztk/0s3YLQC -YuXgN4KrIcMXQIuAXCx407c+pIlT/T1FNn+VQXwi56PYzxQKtlpoKUL3oPQE1d0V -6EoiNk+6UodvyZqpdQu7fXVentRMk1QX7D9otmiiNuX+GSxJhJC2Lyzw65O9EUgG -1yVJon6RkUGtqBqKIuLksKwEr//ELnjmXit4LQKSnqKr0FTCB7seIrKJNyb35Qnq -qy9a/Unhokrmdda1tr6MbqU8l7HmxLuSd/Ky+L0eDNtYv6YfMewtjg0TtAnFyQov -rdXmeq1dy9HLo3Ds4AFz3Gx9076TxcRS/iI= ------END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/sample.pem b/sample-resource-plugin/src/test/resources/security/sample.pem deleted file mode 100644 index 44101f0b37..0000000000 --- a/sample-resource-plugin/src/test/resources/security/sample.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v -dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT -AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl -MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud -yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 -HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr -XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n -dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD -ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R -BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA -AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF -BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo -wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ -KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz -pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi -7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh -hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L -camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg -PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg= ------END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/test-kirk.jks b/sample-resource-plugin/src/test/resources/security/test-kirk.jks deleted file mode 100644 index 6c8c5ef77e20980f8c78295b159256b805da6a28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3766 zcmd^=c{r47AIImJ%`(PV###wuU&o%k$xbMgr4m`Pk2Tv-j4?=zEwY?!X|aVw)I`=A zPAY52Rt6yODkPjhAQ%WsfbL*f;mp!-018Nf*#Q6sf)b!}Nv;s_8gzOC@mTmi+D9F}jyYkhL=#Xk3eYM2csmxKA&W!xAdE{tZ2mEGS z;L%QU`DHcrbdbw$3GsKUvmfQu0Z^?sH7B)!W)eLbG*fXB^G$&6CbCnj4~ z*J>Rkut6vL1EvT!JqAq#X=O~#!JHQ#QVSPuOGlnLrXXB~{{FsGRq?o?I;>^GFEhMB zw;z!v1sXap8nq3zz&+prKs-DRPm*XsS4BaP6Z{8tM~n@m|rxMA=p6*i(w=7 z*2&*Yg-uWU$5|W>>g5h)Fn{3B={`skAJ5_wXB5pDwyj{vG1_{{Y-`wB_i^B!5PA|= zrx=_>rprb&75BQ=J)SKPAJI;?(D#46)o+a?SsR^-&qJjXY2ER8S*1ZvU`t7~M6?NKULuzlAZ8C#X9>8j2;WDY z(TY-^!`&0%67`u|U_-Y(knWVcSlh-kwZQ6KG@S?L`W!iVl>Gyd(LnpMc@C!QeY{(E z)uAwF_CcqH#00}jer2dQk3}R|p^87XCxR8`n4c@g9rASTt9$8}SuGW!!+QQ&w&G!P zvv5Mft<&pzv^&XuuQAj&ieoa*3nI-hx}0`4kym=(cd>?v6yM3v43y@5@;yPeJ_N{@ z622W$@5Z4VqliMF3GAf_RcB;$HX^%cwTCgxg^4)5I0?*&oW|giBB@nUNBO+IX=iON zo~;L}HOwhyeqH4GHvAQ5i=|0c+_5*661aDyT_tr=I#+Zog%!9nRiuBb8m&SS4qp2fv7HJMG zwJFuqV*Hoq3`|Mayml;So|9W4Um6Lu8(k+(Hc2}p@&>?!7!7H~9*O%@BrKNAOa-~e z$e6#G)fJ+wNz5x9zU;#>&V}d z?!F1W_eNN;&LI9$!kWa0Zqa)0CVM4D=x(r>aXgW=XQ)PTRsJJ&MC?WjjoMwLRh`-I z8yD|^&(r#NU|pRpRF%wn&t%X`)8HQe%uxEKnXxIu9yui1s$eH0*YZ^Wvt25yOg6{5 zPefKstjqam-PRDz=&-BVb^xZe>{C{$cza!_sV&3M*l0ocMJVr!l~TlJi4JChDn9Nn zc&la1caY}0P&Ho=r;)l;mKBf$V<6A*R6XC}s98g%I7ZIAFI=e6SqQ4;oevw)nw0%^ zKq9#$;{3R0zJv}#mr7@}e+5-(`{C?^vEE#xb7uBY=X#_1v+@~@l?W@Zaq+Yo9bpu& zR<0us_T`(Q6qp1xYb)Rq;tJ|aTZ&y5xqx<_j-|>1$SEi@3!A|| z9YH<3ub_#ai=2WG_V9iQ!NU8mB|$4ZK3Gr>_s15;6W-XV-*##3TjwoMP&yb zq!L{!sQoUn<_ZWb)BbzloM2Zs1tb=+FBn*$!EQmp3Ml#oe;g0);^XP&_osni`NR1A z0SL>FG{F)8;h%d#4-g0eK+%&0UD-=ghUr~yDQ?!lNE5tKiJ_rjY{@`Q1vjbVAFU;|?Qs;w|1hFx_ z`*jR7rVAU>9*yRSpD1)#aOb!)@ak(5hk;guG$_9)=K8Ie^uOP<63|FjrX2UEcJw07 zD5c?bxHD${?)1+CMgPg@0|kH>4NzJZO*;#rl-xA_8*SHCS}ygKZP7*uHbRtmaTE%n zp7Vt7QIt|IIN?)fyS#8IxKHO$?TeY{DpQl5^kyAd$HH^Aa)SJC+I0!ULR znF7*z6R6~{CCW6M^qKuU!N`I`>YB3i6toA7f7#3%T&$5&wm0nY{&d9(g)LB$%g9dX zf>HfjVn9;)rG-^=)tiGDd<5M4wDHPl@yEGU_whSh78l$%S*WCqjvj^Xt?_VKp0T{pQGU!F;?_^4EMT$__$E zH0hMGQlo@W2p^_tPZsnirl@pGb<#0a^*g5ihYtSzKKx%Wg;i4h8B_c6Z+PPWM!I%g zOr-dLp|0@RV@@&InVrwRJfPT~ZY840gT$Jl4)HP^qcTUWE~1&}C2wS3Sv9pJWiRva zyK}a9ilnrYe7SB$bu~GF&GM`D1h@ukNsJY|Yt>|?q(4gzgSUuGwSIfsmlD)%J2V0@ zTU&-58&x%P)-#Oev2~&}bv^wwRbD$?Enu(jJiuwM3shGOZ{$juY+RGk#m^`!p7+vO zAjWFn1{dq`T?N^TggHmN3~VGf^5?a_)R-cj5yfk-?V<|S)%uKn{YGL)7(~eAhWA56 zj7ZS7amp#qQM;t>%6F)v{1S-Gq>88IPiL?2X9=q_r$vhc4{Pd3$WssBMbZaV2W zu&8||{U99-3!x+JudoA1KSAx^0qg$*YLr)FKtJ($lC@k)W?khPY!~B&3F~Xnxs_WH)b*(MC{~@>r={U4@A6+2p8il>0lojdT`r8~C>rA6;jw^lZK9gk<_y!v za(Rbclc{1;TFBtT`lr|YO0}|UXzh>FLsx6RQUq8=?V4{NR#=oxL2}kHb-ZAfuN Date: Mon, 11 Nov 2024 13:20:06 -0500 Subject: [PATCH 018/122] Updates settings.gradle Signed-off-by: Darshit Chanpura --- settings.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/settings.gradle b/settings.gradle index 0bb3c5639d..1c3e7ff5aa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,3 @@ */ rootProject.name = 'opensearch-security' - -include "sample-resource-plugin" -project(":sample-resource-plugin").name = "opensearch-sample-resource-plugin" From 561e294d87e57fb6fef680eb5482e9c17ca0bff4 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 11 Nov 2024 13:23:30 -0500 Subject: [PATCH 019/122] Adds a sample resource plugin to demonstrate resource access control in action Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 166 ++++++++++++++++ .../java/org/opensearch/sample/Resource.java | 19 ++ .../sample/SampleResourcePlugin.java | 182 ++++++++++++++++++ .../sample/SampleResourceScope.java | 33 ++++ .../actions/create/CreateResourceAction.java | 29 +++ .../actions/create/CreateResourceRequest.java | 50 +++++ .../create/CreateResourceResponse.java | 55 ++++++ .../create/CreateResourceRestAction.java | 55 ++++++ .../sample/actions/create/SampleResource.java | 56 ++++++ .../list/ListAccessibleResourcesAction.java | 29 +++ .../list/ListAccessibleResourcesRequest.java | 39 ++++ .../list/ListAccessibleResourcesResponse.java | 46 +++++ .../ListAccessibleResourcesRestAction.java | 44 +++++ .../actions/share/ShareResourceAction.java | 26 +++ .../actions/share/ShareResourceRequest.java | 52 +++++ .../actions/share/ShareResourceResponse.java | 52 +++++ .../share/ShareResourceRestAction.java | 51 +++++ .../verify/VerifyResourceAccessAction.java | 25 +++ .../verify/VerifyResourceAccessRequest.java | 69 +++++++ .../verify/VerifyResourceAccessResponse.java | 52 +++++ .../VerifyResourceAccessRestAction.java | 52 +++++ .../CreateResourceTransportAction.java | 99 ++++++++++ ...istAccessibleResourcesTransportAction.java | 56 ++++++ .../ShareResourceTransportAction.java | 65 +++++++ .../VerifyResourceAccessTransportAction.java | 58 ++++++ .../plugin-metadata/plugin-security.policy | 3 + .../org.opensearch.plugins.ResourcePlugin | 1 + .../test/resources/security/esnode-key.pem | 28 +++ .../src/test/resources/security/esnode.pem | 25 +++ .../src/test/resources/security/kirk-key.pem | 28 +++ .../src/test/resources/security/kirk.pem | 27 +++ .../src/test/resources/security/root-ca.pem | 28 +++ .../src/test/resources/security/sample.pem | 25 +++ .../src/test/resources/security/test-kirk.jks | Bin 0 -> 3766 bytes settings.gradle | 3 + 35 files changed, 1628 insertions(+) create mode 100644 sample-resource-plugin/build.gradle create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java create mode 100644 sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy create mode 100644 sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin create mode 100644 sample-resource-plugin/src/test/resources/security/esnode-key.pem create mode 100644 sample-resource-plugin/src/test/resources/security/esnode.pem create mode 100644 sample-resource-plugin/src/test/resources/security/kirk-key.pem create mode 100644 sample-resource-plugin/src/test/resources/security/kirk.pem create mode 100644 sample-resource-plugin/src/test/resources/security/root-ca.pem create mode 100644 sample-resource-plugin/src/test/resources/security/sample.pem create mode 100644 sample-resource-plugin/src/test/resources/security/test-kirk.jks diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle new file mode 100644 index 0000000000..e9822c1f22 --- /dev/null +++ b/sample-resource-plugin/build.gradle @@ -0,0 +1,166 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'opensearch.opensearchplugin' +apply plugin: 'opensearch.testclusters' +apply plugin: 'opensearch.java-rest-test' + +import org.opensearch.gradle.test.RestIntegTestTask + + +opensearchplugin { + name 'opensearch-sample-resource-plugin' + description 'Sample plugin that extends OpenSearch Resource Plugin' + classname 'org.opensearch.sample.SampleResourcePlugin' +} + +ext { + projectSubstitutions = [:] + licenseFile = rootProject.file('LICENSE.txt') + noticeFile = rootProject.file('NOTICE.txt') +} + +repositories { + mavenLocal() + mavenCentral() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } +} + +dependencies { +} + +def es_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile +es_tmp_dir.mkdirs() + +File repo = file("$buildDir/testclusters/repo") +def _numNodes = findProperty('numNodes') as Integer ?: 1 + +licenseHeaders.enabled = true +validateNebulaPom.enabled = false +testingConventions.enabled = false +loggerUsageCheck.enabled = false + +javaRestTest.dependsOn(rootProject.assemble) +javaRestTest { + systemProperty 'tests.security.manager', 'false' +} +testClusters.javaRestTest { + testDistribution = 'INTEG_TEST' +} + +task integTest(type: RestIntegTestTask) { + description = "Run tests against a cluster" + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath +} +tasks.named("check").configure { dependsOn(integTest) } + +integTest { + if (project.hasProperty('excludeTests')) { + project.properties['excludeTests']?.replaceAll('\\s', '')?.split('[,;]')?.each { + exclude "${it}" + } + } + systemProperty 'tests.security.manager', 'false' + systemProperty 'java.io.tmpdir', es_tmp_dir.absolutePath + + systemProperty "https", System.getProperty("https") + systemProperty "user", System.getProperty("user") + systemProperty "password", System.getProperty("password") + // Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for + // requests. The 'doFirst' delays reading the debug setting on the cluster till execution time. + doFirst { + // Tell the test JVM if the cluster JVM is running under a debugger so that tests can + // use longer timeouts for requests. + def isDebuggingCluster = getDebug() || System.getProperty("test.debug") != null + systemProperty 'cluster.debug', isDebuggingCluster + // Set number of nodes system property to be used in tests + systemProperty 'cluster.number_of_nodes', "${_numNodes}" + // There seems to be an issue when running multi node run or integ tasks with unicast_hosts + // not being written, the waitForAllConditions ensures it's written + getClusters().forEach { cluster -> + cluster.waitForAllConditions() + } + } + + // The -Dcluster.debug option makes the cluster debuggable; this makes the tests debuggable + if (System.getProperty("test.debug") != null) { + jvmArgs '-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=8000' + } + if (System.getProperty("tests.rest.bwcsuite") == null) { + filter { + excludeTestsMatching "org.opensearch.security.sampleextension.bwc.*IT" + } + } +} +project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build')) +Zip bundle = (Zip) project.getTasks().getByName("bundlePlugin"); +Zip rootBundle = (Zip) rootProject.getTasks().getByName("bundlePlugin"); +integTest.dependsOn(bundle) +integTest.getClusters().forEach{c -> { + c.plugin(rootProject.getObjects().fileProperty().value(rootBundle.getArchiveFile())) + c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile())) +}} + +testClusters.integTest { + testDistribution = 'INTEG_TEST' + + // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1 + if (_numNodes > 1) numberOfNodes = _numNodes + // When running integration tests it doesn't forward the --debug-jvm to the cluster anymore + // i.e. we have to use a custom property to flag when we want to debug OpenSearch JVM + // since we also support multi node integration tests we increase debugPort per node + if (System.getProperty("cluster.debug") != null) { + def debugPort = 5005 + nodes.forEach { node -> + node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=*:${debugPort}") + debugPort += 1 + } + } + setting 'path.repo', repo.absolutePath +} + +afterEvaluate { + testClusters.integTest.nodes.each { node -> + def plugins = node.plugins + def firstPlugin = plugins.get(0) + if (firstPlugin.provider == project.bundlePlugin.archiveFile) { + plugins.remove(0) + plugins.add(firstPlugin) + } + + node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem")) + node.extraConfigFile("kirk-key.pem", file("src/test/resources/security/kirk-key.pem")) + node.extraConfigFile("esnode.pem", file("src/test/resources/security/esnode.pem")) + node.extraConfigFile("esnode-key.pem", file("src/test/resources/security/esnode-key.pem")) + node.extraConfigFile("root-ca.pem", file("src/test/resources/security/root-ca.pem")) + node.setting("plugins.security.ssl.transport.pemcert_filepath", "esnode.pem") + node.setting("plugins.security.ssl.transport.pemkey_filepath", "esnode-key.pem") + node.setting("plugins.security.ssl.transport.pemtrustedcas_filepath", "root-ca.pem") + node.setting("plugins.security.ssl.transport.enforce_hostname_verification", "false") + node.setting("plugins.security.ssl.http.enabled", "true") + node.setting("plugins.security.ssl.http.pemcert_filepath", "esnode.pem") + node.setting("plugins.security.ssl.http.pemkey_filepath", "esnode-key.pem") + node.setting("plugins.security.ssl.http.pemtrustedcas_filepath", "root-ca.pem") + node.setting("plugins.security.allow_unsafe_democertificates", "true") + node.setting("plugins.security.allow_default_init_securityindex", "true") + node.setting("plugins.security.authcz.admin_dn", "\n - CN=kirk,OU=client,O=client,L=test,C=de") + node.setting("plugins.security.audit.type", "internal_opensearch") + node.setting("plugins.security.enable_snapshot_restore_privilege", "true") + node.setting("plugins.security.check_snapshot_restore_write_privileges", "true") + node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]") + } +} + +run { + doFirst { + // There seems to be an issue when running multi node run or integ tasks with unicast_hosts + // not being written, the waitForAllConditions ensures it's written + getClusters().forEach { cluster -> + cluster.waitForAllConditions() + } + } + useCluster testClusters.integTest +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java new file mode 100644 index 0000000000..36e74f1624 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java @@ -0,0 +1,19 @@ +/* + * 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.sample; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.xcontent.ToXContentFragment; + +public abstract class Resource implements NamedWriteable, ToXContentFragment { + protected abstract String getResourceIndex(); +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java new file mode 100644 index 0000000000..74a8378887 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -0,0 +1,182 @@ +/* + * Copyright OpenSearch Contributors + * 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. + */ +package org.opensearch.sample; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.action.ActionRequest; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.lifecycle.Lifecycle; +import org.opensearch.common.lifecycle.LifecycleComponent; +import org.opensearch.common.lifecycle.LifecycleListener; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourcePlugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import org.opensearch.sample.actions.create.CreateResourceAction; +import org.opensearch.sample.actions.create.CreateResourceRestAction; +import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; +import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction; +import org.opensearch.sample.actions.share.ShareResourceAction; +import org.opensearch.sample.actions.share.ShareResourceRestAction; +import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; +import org.opensearch.sample.actions.verify.VerifyResourceAccessRestAction; +import org.opensearch.sample.transport.CreateResourceTransportAction; +import org.opensearch.sample.transport.ListAccessibleResourcesTransportAction; +import org.opensearch.sample.transport.ShareResourceTransportAction; +import org.opensearch.sample.transport.VerifyResourceAccessTransportAction; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +/** + * Sample Resource plugin. + * It uses ".sample_resources" index to manage its resources, and exposes a REST API + * + */ +public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { + private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); + + public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; + + public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); + + private Client client; + + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + this.client = client; + log.info("Loaded SampleResourcePlugin components."); + return Collections.emptyList(); + } + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return List.of( + new CreateResourceRestAction(), + new ListAccessibleResourcesRestAction(), + new VerifyResourceAccessRestAction(), + new ShareResourceRestAction() + ); + } + + @Override + public List> getActions() { + return List.of( + new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class), + new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class), + new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class), + new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class) + ); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Example index with resources"); + return Collections.singletonList(systemIndexDescriptor); + } + + @Override + public String getResourceType() { + return ""; + } + + @Override + public String getResourceIndex() { + return RESOURCE_INDEX_NAME; + } + + @Override + public Collection> getGuiceServiceClasses() { + final List> services = new ArrayList<>(1); + services.add(GuiceHolder.class); + return services; + } + + public static class GuiceHolder implements LifecycleComponent { + + private static ResourceService resourceService; + + @Inject + public GuiceHolder(final ResourceService resourceService) { + GuiceHolder.resourceService = resourceService; + } + + public static ResourceService getResourceService() { + return resourceService; + } + + @Override + public void close() {} + + @Override + public Lifecycle.State lifecycleState() { + return null; + } + + @Override + public void addLifecycleListener(LifecycleListener listener) {} + + @Override + public void removeLifecycleListener(LifecycleListener listener) {} + + @Override + public void start() {} + + @Override + public void stop() {} + + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java new file mode 100644 index 0000000000..90df0d3764 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java @@ -0,0 +1,33 @@ +/* + * 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.sample; + +import org.opensearch.accesscontrol.resources.ResourceAccessScope; + +/** + * This class demonstrates a sample implementation of Basic Access Scopes to fit each plugin's use-case. + * The plugin then uses this scope when seeking access evaluation for a user on a particular resource. + */ +public enum SampleResourceScope implements ResourceAccessScope { + + SAMPLE_FULL_ACCESS("sample_full_access"); + + private final String name; + + SampleResourceScope(String scopeName) { + this.name = scopeName; + } + + public String getName() { + return name; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java new file mode 100644 index 0000000000..e7c02278ab --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.create; + +import org.opensearch.action.ActionType; + +/** + * Action to create a sample resource + */ +public class CreateResourceAction extends ActionType { + /** + * Create sample resource action instance + */ + public static final CreateResourceAction INSTANCE = new CreateResourceAction(); + /** + * Create sample resource action name + */ + public static final String NAME = "cluster:admin/sample-resource-plugin/create"; + + private CreateResourceAction() { + super(NAME, CreateResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java new file mode 100644 index 0000000000..b31a4b7f2b --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java @@ -0,0 +1,50 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.create; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.sample.Resource; + +/** + * Request object for CreateSampleResource transport action + */ +public class CreateResourceRequest extends ActionRequest { + + private final Resource resource; + + /** + * Default constructor + */ + public CreateResourceRequest(Resource resource) { + this.resource = resource; + } + + public CreateResourceRequest(StreamInput in) throws IOException { + this.resource = in.readNamedWriteable(Resource.class); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + resource.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public Resource getResource() { + return this.resource; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java new file mode 100644 index 0000000000..6b966ed08d --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.create; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +/** + * Response to a CreateSampleResourceRequest + */ +public class CreateResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public CreateResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public CreateResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java new file mode 100644 index 0000000000..86346cc279 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.create; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.POST; + +public class CreateResourceRestAction extends BaseRestHandler { + + public CreateResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/resource")); + } + + @Override + public String getName() { + return "create_sample_resource"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String name = (String) source.get("name"); + SampleResource resource = new SampleResource(); + resource.setName(name); + final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest(resource); + return channel -> client.executeLocally( + CreateResourceAction.INSTANCE, + createSampleResourceRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java new file mode 100644 index 0000000000..1566abfe69 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java @@ -0,0 +1,56 @@ +/* + * 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.sample.actions.create; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.sample.Resource; + +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +public class SampleResource extends Resource { + + private String name; + + public SampleResource() {} + + SampleResource(StreamInput in) throws IOException { + this.name = in.readString(); + } + + @Override + public String getResourceIndex() { + return RESOURCE_INDEX_NAME; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("name", name).endObject(); + } + + @Override + public void writeTo(StreamOutput streamOutput) throws IOException { + streamOutput.writeString(name); + } + + @Override + public String getWriteableName() { + return "sample_resource"; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java new file mode 100644 index 0000000000..b4e9e29e22 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.list; + +import org.opensearch.action.ActionType; + +/** + * Action to list sample resources + */ +public class ListAccessibleResourcesAction extends ActionType { + /** + * List sample resource action instance + */ + public static final ListAccessibleResourcesAction INSTANCE = new ListAccessibleResourcesAction(); + /** + * List sample resource action name + */ + public static final String NAME = "cluster:admin/sample-resource-plugin/list"; + + private ListAccessibleResourcesAction() { + super(NAME, ListAccessibleResourcesResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java new file mode 100644 index 0000000000..b4c0961774 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.list; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +/** + * Request object for ListSampleResource transport action + */ +public class ListAccessibleResourcesRequest extends ActionRequest { + + public ListAccessibleResourcesRequest() {} + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public ListAccessibleResourcesRequest(final StreamInput in) throws IOException {} + + @Override + public void writeTo(final StreamOutput out) throws IOException {} + + @Override + public ActionRequestValidationException validate() { + return null; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java new file mode 100644 index 0000000000..47a8f88e4e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java @@ -0,0 +1,46 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.list; + +import java.io.IOException; +import java.util.List; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +/** + * Response to a ListAccessibleResourcesRequest + */ +public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { + private final List resourceIds; + + public ListAccessibleResourcesResponse(List resourceIds) { + this.resourceIds = resourceIds; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringArray(resourceIds.toArray(new String[0])); + } + + public ListAccessibleResourcesResponse(final StreamInput in) throws IOException { + resourceIds = in.readStringList(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("resource-ids", resourceIds); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java new file mode 100644 index 0000000000..bb921fce00 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.list; + +import java.util.List; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class ListAccessibleResourcesRestAction extends BaseRestHandler { + + public ListAccessibleResourcesRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/resource")); + } + + @Override + public String getName() { + return "list_sample_resources"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(); + return channel -> client.executeLocally( + ListAccessibleResourcesAction.INSTANCE, + listAccessibleResourcesRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java new file mode 100644 index 0000000000..d362b1927c --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import org.opensearch.action.ActionType; + +public class ShareResourceAction extends ActionType { + /** + * List sample resource action instance + */ + public static final ShareResourceAction INSTANCE = new ShareResourceAction(); + /** + * List sample resource action name + */ + public static final String NAME = "cluster:admin/sample-resource-plugin/share"; + + private ShareResourceAction() { + super(NAME, ShareResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java new file mode 100644 index 0000000000..01866fd516 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import java.io.IOException; + +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +public class ShareResourceRequest extends ActionRequest { + + private final String resourceId; + private final ShareWith shareWith; + + public ShareResourceRequest(String resourceId, ShareWith shareWith) { + this.resourceId = resourceId; + this.shareWith = shareWith; + } + + public ShareResourceRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.shareWith = in.readNamedWriteable(ShareWith.class); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeNamedWriteable(shareWith); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public ShareWith getShareWith() { + return shareWith; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java new file mode 100644 index 0000000000..a6a85d206d --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class ShareResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public ShareResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public ShareResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java new file mode 100644 index 0000000000..347fb49e68 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.share; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class ShareResourceRestAction extends BaseRestHandler { + + public ShareResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/share/{resource_id}")); + } + + @Override + public String getName() { + return "share_sample_resources"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + ShareWith shareWith = (ShareWith) source.get("share_with"); + final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, shareWith); + return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java new file mode 100644 index 0000000000..1378d561f5 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import org.opensearch.action.ActionType; + +/** + * Action to verify resource access for current user + */ +public class VerifyResourceAccessAction extends ActionType { + + public static final VerifyResourceAccessAction INSTANCE = new VerifyResourceAccessAction(); + + public static final String NAME = "cluster:admin/sample-resource-plugin/verify/resource_access"; + + private VerifyResourceAccessAction() { + super(NAME, VerifyResourceAccessResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java new file mode 100644 index 0000000000..e9b20118db --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java @@ -0,0 +1,69 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +public class VerifyResourceAccessRequest extends ActionRequest { + + private final String resourceId; + + private final String sourceIdx; + + private final String scope; + + /** + * Default constructor + */ + public VerifyResourceAccessRequest(String resourceId, String sourceIdx, String scope) { + this.resourceId = resourceId; + this.sourceIdx = sourceIdx; + this.scope = scope; + } + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public VerifyResourceAccessRequest(final StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.sourceIdx = in.readString(); + this.scope = in.readString(); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeString(sourceIdx); + out.writeString(scope); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public String getSourceIdx() { + return sourceIdx; + } + + public String getScope() { + return scope; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java new file mode 100644 index 0000000000..660ac03f71 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class VerifyResourceAccessResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public VerifyResourceAccessResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public VerifyResourceAccessResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java new file mode 100644 index 0000000000..34bfed4e9f --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.verify; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.POST; + +public class VerifyResourceAccessRestAction extends BaseRestHandler { + + public VerifyResourceAccessRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/verify_resource_access")); + } + + @Override + public String getName() { + return "verify_resource_access"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceIdx = (String) source.get("resource_idx"); + String sourceIdx = (String) source.get("source_idx"); + String scope = (String) source.get("scope"); + + // final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest<>(resource); + return channel -> client.executeLocally(VerifyResourceAccessAction.INSTANCE, null, new RestToXContentListener<>(channel)); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java new file mode 100644 index 0000000000..8bff7b44a3 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -0,0 +1,99 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import java.io.IOException; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.SharedWithScope; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.sample.Resource; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.SampleResourceScope; +import org.opensearch.sample.actions.create.CreateResourceAction; +import org.opensearch.sample.actions.create.CreateResourceRequest; +import org.opensearch.sample.actions.create.CreateResourceResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for CreateSampleResource. + */ +public class CreateResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(CreateResourceTransportAction.class); + + private final TransportService transportService; + private final Client nodeClient; + + @Inject + public CreateResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(CreateResourceAction.NAME, transportService, actionFilters, CreateResourceRequest::new); + this.transportService = transportService; + this.nodeClient = nodeClient; + } + + @Override + protected void doExecute(Task task, CreateResourceRequest request, ActionListener listener) { + try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + createResource(request, listener); + listener.onResponse(new CreateResourceResponse("Resource " + request.getResource() + " created successfully.")); + } catch (Exception e) { + log.info("Failed to create resource", e); + listener.onFailure(e); + } + } + + private void createResource(CreateResourceRequest request, ActionListener listener) { + Resource sample = request.getResource(); + try { + IndexRequest ir = nodeClient.prepareIndex(RESOURCE_INDEX_NAME) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setSource(sample.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .request(); + + log.warn("Index Request: {}", ir.toString()); + + ActionListener irListener = getIndexResponseActionListener(listener); + nodeClient.index(ir, irListener); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static ActionListener getIndexResponseActionListener(ActionListener listener) { + SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(List.of(), List.of(), List.of()); + SharedWithScope sharedWithScope = new SharedWithScope(SampleResourceScope.SAMPLE_FULL_ACCESS.getName(), sharedWithPerScope); + ShareWith shareWith = new ShareWith(List.of(sharedWithScope)); + return ActionListener.wrap(idxResponse -> { + log.info("Created resource: {}", idxResponse.toString()); + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + ResourceSharing sharing = rs.getResourceAccessControlPlugin().shareWith(idxResponse.getId(), idxResponse.getIndex(), shareWith); + log.info("Created resource sharing entry: {}", sharing.toString()); + }, listener::onFailure); + } + +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java new file mode 100644 index 0000000000..d56eb6d291 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; +import org.opensearch.sample.actions.list.ListAccessibleResourcesRequest; +import org.opensearch.sample.actions.list.ListAccessibleResourcesResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for ListSampleResource. + */ +public class ListAccessibleResourcesTransportAction extends HandledTransportAction< + ListAccessibleResourcesRequest, + ListAccessibleResourcesResponse> { + private static final Logger log = LogManager.getLogger(ListAccessibleResourcesTransportAction.class); + + @Inject + public ListAccessibleResourcesTransportAction(TransportService transportService, ActionFilters actionFilters) { + super(ListAccessibleResourcesAction.NAME, transportService, actionFilters, ListAccessibleResourcesRequest::new); + } + + @Override + protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { + try { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); + log.info("Successfully fetched accessible resources for current user"); + listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); + } catch (Exception e) { + log.info("Failed to list accessible resources for current user: ", e); + listener.onFailure(e); + } + + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java new file mode 100644 index 0000000000..ccbfc31b78 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java @@ -0,0 +1,65 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.share.ShareResourceAction; +import org.opensearch.sample.actions.share.ShareResourceRequest; +import org.opensearch.sample.actions.share.ShareResourceResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; + +/** + * Transport action for CreateSampleResource. + */ +public class ShareResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); + + @Inject + public ShareResourceTransportAction(TransportService transportService, ActionFilters actionFilters) { + super(ShareResourceAction.NAME, transportService, actionFilters, ShareResourceRequest::new); + } + + @Override + protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { + try { + shareResource(request); + listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); + } catch (Exception e) { + listener.onFailure(e); + } + } + + private void shareResource(ShareResourceRequest request) { + try { + ShareWith shareWith = new ShareWith(List.of()); + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + ResourceSharing sharing = rs.getResourceAccessControlPlugin() + .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, shareWith); + log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); + } catch (Exception e) { + log.info("Failed to share resource {}", request.getResourceId(), e); + throw e; + } + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java new file mode 100644 index 0000000000..947dcec59e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; +import org.opensearch.sample.actions.verify.VerifyResourceAccessRequest; +import org.opensearch.sample.actions.verify.VerifyResourceAccessResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class VerifyResourceAccessTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class); + + @Inject + public VerifyResourceAccessTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(VerifyResourceAccessAction.NAME, transportService, actionFilters, VerifyResourceAccessRequest::new); + } + + @Override + protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) { + try { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + boolean hasRequestedScopeAccess = rs.getResourceAccessControlPlugin() + .hasPermission(request.getResourceId(), request.getSourceIdx(), request.getScope()); + + StringBuilder sb = new StringBuilder(); + sb.append("User does"); + sb.append(hasRequestedScopeAccess ? " " : " not "); + sb.append("have requested scope "); + sb.append(request.getScope()); + sb.append(" access to "); + sb.append(request.getResourceId()); + + log.info(sb.toString()); + listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); + } catch (Exception e) { + log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); + listener.onFailure(e); + } + } + +} diff --git a/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy b/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy new file mode 100644 index 0000000000..a5dfc33a87 --- /dev/null +++ b/sample-resource-plugin/src/main/plugin-metadata/plugin-security.policy @@ -0,0 +1,3 @@ +grant { + permission java.lang.RuntimePermission "getClassLoader"; +}; \ No newline at end of file diff --git a/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin b/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin new file mode 100644 index 0000000000..1ca89eaf74 --- /dev/null +++ b/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin @@ -0,0 +1 @@ +org.opensearch.sample.SampleResourcePlugin \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/esnode-key.pem b/sample-resource-plugin/src/test/resources/security/esnode-key.pem new file mode 100644 index 0000000000..e90562be43 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/esnode-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCm93kXteDQHMAv +bUPNPW5pyRHKDD42XGWSgq0k1D29C/UdyL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0 +o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0HGkn47XVu3EwbfrTENg3jFu+Oem6a/50 +1SzITzJWtS0cn2dIFOBimTVpT/4Zv5qrXA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1 +MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8ndibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b +6l+KLo3IKpfTbAIJXIO+M67FLtWKtttDao94B069skzKk6FPgW/OZh6PRCD0oxOa +vV+ld2SjAgMBAAECggEAQK1+uAOZeaSZggW2jQut+MaN4JHLi61RH2cFgU3COLgo +FIiNjFn8f2KKU3gpkt1It8PjlmprpYut4wHI7r6UQfuv7ZrmncRiPWHm9PB82+ZQ +5MXYqj4YUxoQJ62Cyz4sM6BobZDrjG6HHGTzuwiKvHHkbsEE9jQ4E5m7yfbVvM0O +zvwrSOM1tkZihKSTpR0j2+taji914tjBssbn12TMZQL5ItGnhR3luY8mEwT9MNkZ +xg0VcREoAH+pu9FE0vPUgLVzhJ3be7qZTTSRqv08bmW+y1plu80GbppePcgYhEow +dlW4l6XPJaHVSn1lSFHE6QAx6sqiAnBz0NoTPIaLyQKBgQDZqDOlhCRciMRicSXn +7yid9rhEmdMkySJHTVFOidFWwlBcp0fGxxn8UNSBcXdSy7GLlUtH41W9PWl8tp9U +hQiiXORxOJ7ZcB80uNKXF01hpPj2DpFPWyHFxpDkWiTAYpZl68rOlYujxZUjJIej +VvcykBC2BlEOG9uZv2kxcqLyJwKBgQDEYULTxaTuLIa17wU3nAhaainKB3vHxw9B +Ksy5p3ND43UNEKkQm7K/WENx0q47TA1mKD9i+BhaLod98mu0YZ+BCUNgWKcBHK8c +uXpauvM/pLhFLXZ2jvEJVpFY3J79FSRK8bwE9RgKfVKMMgEk4zOyZowS8WScOqiy +hnQn1vKTJQKBgElhYuAnl9a2qXcC7KOwRsJS3rcKIVxijzL4xzOyVShp5IwIPbOv +hnxBiBOH/JGmaNpFYBcBdvORE9JfA4KMQ2fx53agfzWRjoPI1/7mdUk5RFI4gRb/ +A3jZRBoopgFSe6ArCbnyQxzYzToG48/Wzwp19ZxYrtUR4UyJct6f5n27AoGBAJDh +KIpQQDOvCdtjcbfrF4aM2DPCfaGPzENJriwxy6oEPzDaX8Bu/dqI5Ykt43i/zQrX +GpyLaHvv4+oZVTiI5UIvcVO9U8hQPyiz9f7F+fu0LHZs6f7hyhYXlbe3XFxeop3f +5dTKdWgXuTTRF2L9dABkA2deS9mutRKwezWBMQk5AoGBALPtX0FrT1zIosibmlud +tu49A/0KZu4PBjrFMYTSEWGNJez3Fb2VsJwylVl6HivwbP61FhlYfyksCzQQFU71 ++x7Nmybp7PmpEBECr3deoZKQ/acNHn0iwb0It+YqV5+TquQebqgwK6WCLsMuiYKT +bg/ch9Rhxbq22yrVgWHh6epp +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/esnode.pem b/sample-resource-plugin/src/test/resources/security/esnode.pem new file mode 100644 index 0000000000..44101f0b37 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/esnode.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl +MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud +yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 +HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr +XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n +dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD +ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R +BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA +AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo +wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ +KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz +pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi +7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh +hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L +camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg +PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/kirk-key.pem b/sample-resource-plugin/src/test/resources/security/kirk-key.pem new file mode 100644 index 0000000000..1949c26139 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/kirk-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCVXDgEJQorgfXp +gpY0TgF55bD2xuzxN5Dc9rDfgWxrsOvOloMpd7k6FR71bKWjJi1KptSmM/cDElky +AWYKSfYWGiGxsQ+EQW+6kwCfEOHXQldn+0+JcWqP+osSPjtJfwRvRN5kRqP69MPo +7U0N2kdqenqMWjmG1chDGLRSOEGU5HIBiDxsZtOcvMaJ8b1eaW0lvS+6gFQ80AvB +GBkDDCOHHLtDXBylrZk2CQP8AzxNicIZ4B8G3CG3OHA8+nBtEtxZoIihrrkqlMt+ +b/5N8u8zB0Encew0kdrc4R/2wS//ahr6U+9Siq8T7WsUtGwKj3BJClg6OyDJRhlu +y2gFnxoPAgMBAAECggEAP5TOycDkx+megAWVoHV2fmgvgZXkBrlzQwUG/VZQi7V4 +ZGzBMBVltdqI38wc5MtbK3TCgHANnnKgor9iq02Z4wXDwytPIiti/ycV9CDRKvv0 +TnD2hllQFjN/IUh5n4thHWbRTxmdM7cfcNgX3aZGkYbLBVVhOMtn4VwyYu/Mxy8j +xClZT2xKOHkxqwmWPmdDTbAeZIbSv7RkIGfrKuQyUGUaWhrPslvYzFkYZ0umaDgQ +OAthZew5Bz3OfUGOMPLH61SVPuJZh9zN1hTWOvT65WFWfsPd2yStI+WD/5PU1Doo +1RyeHJO7s3ug8JPbtNJmaJwHe9nXBb/HXFdqb976yQKBgQDNYhpu+MYSYupaYqjs +9YFmHQNKpNZqgZ4ceRFZ6cMJoqpI5dpEMqToFH7tpor72Lturct2U9nc2WR0HeEs +/6tiptyMPTFEiMFb1opQlXF2ae7LeJllntDGN0Q6vxKnQV+7VMcXA0Y8F7tvGDy3 +qJu5lfvB1mNM2I6y/eMxjBuQhwKBgQC6K41DXMFro0UnoO879pOQYMydCErJRmjG +/tZSy3Wj4KA/QJsDSViwGfvdPuHZRaG9WtxdL6kn0w1exM9Rb0bBKl36lvi7o7xv +M+Lw9eyXMkww8/F5d7YYH77gIhGo+RITkKI3+5BxeBaUnrGvmHrpmpgRXWmINqr0 +0jsnN3u0OQKBgCf45vIgItSjQb8zonLz2SpZjTFy4XQ7I92gxnq8X0Q5z3B+o7tQ +K/4rNwTju/sGFHyXAJlX+nfcK4vZ4OBUJjP+C8CTjEotX4yTNbo3S6zjMyGQqDI5 +9aIOUY4pb+TzeUFJX7If5gR+DfGyQubvvtcg1K3GHu9u2l8FwLj87sRzAoGAflQF +RHuRiG+/AngTPnZAhc0Zq0kwLkpH2Rid6IrFZhGLy8AUL/O6aa0IGoaMDLpSWUJp +nBY2S57MSM11/MVslrEgGmYNnI4r1K25xlaqV6K6ztEJv6n69327MS4NG8L/gCU5 +3pEm38hkUi8pVYU7in7rx4TCkrq94OkzWJYurAkCgYATQCL/rJLQAlJIGulp8s6h +mQGwy8vIqMjAdHGLrCS35sVYBXG13knS52LJHvbVee39AbD5/LlWvjJGlQMzCLrw +F7oILW5kXxhb8S73GWcuMbuQMFVHFONbZAZgn+C9FW4l7XyRdkrbR1MRZ2km8YMs +/AHmo368d4PSNRMMzLHw8Q== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/kirk.pem b/sample-resource-plugin/src/test/resources/security/kirk.pem new file mode 100644 index 0000000000..36b7e19a75 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/kirk.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEmDCCA4CgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLcwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzA0MjRaFw0zNDAyMTcxNzA0MjRaME0xCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ8wDQYDVQQKDAZjbGllbnQxDzANBgNVBAsMBmNs +aWVudDENMAsGA1UEAwwEa2lyazCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAJVcOAQlCiuB9emCljROAXnlsPbG7PE3kNz2sN+BbGuw686Wgyl3uToVHvVs +paMmLUqm1KYz9wMSWTIBZgpJ9hYaIbGxD4RBb7qTAJ8Q4ddCV2f7T4lxao/6ixI+ +O0l/BG9E3mRGo/r0w+jtTQ3aR2p6eoxaOYbVyEMYtFI4QZTkcgGIPGxm05y8xonx +vV5pbSW9L7qAVDzQC8EYGQMMI4ccu0NcHKWtmTYJA/wDPE2JwhngHwbcIbc4cDz6 +cG0S3FmgiKGuuSqUy35v/k3y7zMHQSdx7DSR2tzhH/bBL/9qGvpT71KKrxPtaxS0 +bAqPcEkKWDo7IMlGGW7LaAWfGg8CAwEAAaOCASswggEnMAwGA1UdEwEB/wQCMAAw +DgYDVR0PAQH/BAQDAgXgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMB0GA1UdDgQW +BBSjMS8tgguX/V7KSGLoGg7K6XMzIDCBzwYDVR0jBIHHMIHEgBQXh9+gWutmEqfV +0Pi6EkU8tysAnKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS +JomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAf +BgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBs +ZSBDb20gSW5jLiBSb290IENBghQNZAmZZn3EFOxBR4630XlhI+mo4jANBgkqhkiG +9w0BAQsFAAOCAQEACEUPPE66/Ot3vZqRGpjDjPHAdtOq+ebaglQhvYcnDw8LOZm8 +Gbh9M88CiO6UxC8ipQLTPh2yyeWArkpJzJK/Pi1eoF1XLiAa0sQ/RaJfQWPm9dvl +1ZQeK5vfD4147b3iBobwEV+CR04SKow0YeEEzAJvzr8YdKI6jqr+2GjjVqzxvRBy +KRVHWCFiR7bZhHGLq3br8hSu0hwjb3oGa1ZI8dui6ujyZt6nm6BoEkau3G/6+zq9 +E6vX3+8Fj4HKCAL6i0SwfGmEpTNp5WUhqibK/fMhhmMT4Mx6MxkT+OFnIjdUU0S/ +e3kgnG8qjficUr38CyEli1U0M7koIXUZI7r+LQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/root-ca.pem b/sample-resource-plugin/src/test/resources/security/root-ca.pem new file mode 100644 index 0000000000..d33f5f7216 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/root-ca.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIExjCCA66gAwIBAgIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzAwMzZaFw0zNDAyMTcxNzAwMzZaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEPyN7J9VGPyJcQmCBl5TGwfSzvVdWwoQU +j9aEsdfFJ6pBCDQSsj8Lv4RqL0dZra7h7SpZLLX/YZcnjikrYC+rP5OwsI9xEE/4 +U98CsTBPhIMgqFK6SzNE5494BsAk4cL72dOOc8tX19oDS/PvBULbNkthQ0aAF1dg +vbrHvu7hq7LisB5ZRGHVE1k/AbCs2PaaKkn2jCw/b+U0Ml9qPuuEgz2mAqJDGYoA +WSR4YXrOcrmPuRqbws464YZbJW898/0Pn/U300ed+4YHiNYLLJp51AMkR4YEw969 +VRPbWIvLrd0PQBooC/eLrL6rvud/GpYhdQEUx8qcNCKd4bz3OaQ5AgMBAAGjggEW +MIIBEjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQU +F4ffoFrrZhKn1dD4uhJFPLcrAJwwgc8GA1UdIwSBxzCBxIAUF4ffoFrrZhKn1dD4 +uhJFPLcrAJyhgZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJ +k/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYD +VQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUg +Q29tIEluYy4gUm9vdCBDQYIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcN +AQELBQADggEBAL3Q3AHUhMiLUy6OlLSt8wX9I2oNGDKbBu0atpUNDztk/0s3YLQC +YuXgN4KrIcMXQIuAXCx407c+pIlT/T1FNn+VQXwi56PYzxQKtlpoKUL3oPQE1d0V +6EoiNk+6UodvyZqpdQu7fXVentRMk1QX7D9otmiiNuX+GSxJhJC2Lyzw65O9EUgG +1yVJon6RkUGtqBqKIuLksKwEr//ELnjmXit4LQKSnqKr0FTCB7seIrKJNyb35Qnq +qy9a/Unhokrmdda1tr6MbqU8l7HmxLuSd/Ky+L0eDNtYv6YfMewtjg0TtAnFyQov +rdXmeq1dy9HLo3Ds4AFz3Gx9076TxcRS/iI= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/sample.pem b/sample-resource-plugin/src/test/resources/security/sample.pem new file mode 100644 index 0000000000..44101f0b37 --- /dev/null +++ b/sample-resource-plugin/src/test/resources/security/sample.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT +AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl +MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud +yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0 +HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr +XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n +dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD +ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R +BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA +AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo +wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ +KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz +pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi +7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh +hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L +camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg +PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/sample-resource-plugin/src/test/resources/security/test-kirk.jks b/sample-resource-plugin/src/test/resources/security/test-kirk.jks new file mode 100644 index 0000000000000000000000000000000000000000..6c8c5ef77e20980f8c78295b159256b805da6a28 GIT binary patch literal 3766 zcmd^=c{r47AIImJ%`(PV###wuU&o%k$xbMgr4m`Pk2Tv-j4?=zEwY?!X|aVw)I`=A zPAY52Rt6yODkPjhAQ%WsfbL*f;mp!-018Nf*#Q6sf)b!}Nv;s_8gzOC@mTmi+D9F}jyYkhL=#Xk3eYM2csmxKA&W!xAdE{tZ2mEGS z;L%QU`DHcrbdbw$3GsKUvmfQu0Z^?sH7B)!W)eLbG*fXB^G$&6CbCnj4~ z*J>Rkut6vL1EvT!JqAq#X=O~#!JHQ#QVSPuOGlnLrXXB~{{FsGRq?o?I;>^GFEhMB zw;z!v1sXap8nq3zz&+prKs-DRPm*XsS4BaP6Z{8tM~n@m|rxMA=p6*i(w=7 z*2&*Yg-uWU$5|W>>g5h)Fn{3B={`skAJ5_wXB5pDwyj{vG1_{{Y-`wB_i^B!5PA|= zrx=_>rprb&75BQ=J)SKPAJI;?(D#46)o+a?SsR^-&qJjXY2ER8S*1ZvU`t7~M6?NKULuzlAZ8C#X9>8j2;WDY z(TY-^!`&0%67`u|U_-Y(knWVcSlh-kwZQ6KG@S?L`W!iVl>Gyd(LnpMc@C!QeY{(E z)uAwF_CcqH#00}jer2dQk3}R|p^87XCxR8`n4c@g9rASTt9$8}SuGW!!+QQ&w&G!P zvv5Mft<&pzv^&XuuQAj&ieoa*3nI-hx}0`4kym=(cd>?v6yM3v43y@5@;yPeJ_N{@ z622W$@5Z4VqliMF3GAf_RcB;$HX^%cwTCgxg^4)5I0?*&oW|giBB@nUNBO+IX=iON zo~;L}HOwhyeqH4GHvAQ5i=|0c+_5*661aDyT_tr=I#+Zog%!9nRiuBb8m&SS4qp2fv7HJMG zwJFuqV*Hoq3`|Mayml;So|9W4Um6Lu8(k+(Hc2}p@&>?!7!7H~9*O%@BrKNAOa-~e z$e6#G)fJ+wNz5x9zU;#>&V}d z?!F1W_eNN;&LI9$!kWa0Zqa)0CVM4D=x(r>aXgW=XQ)PTRsJJ&MC?WjjoMwLRh`-I z8yD|^&(r#NU|pRpRF%wn&t%X`)8HQe%uxEKnXxIu9yui1s$eH0*YZ^Wvt25yOg6{5 zPefKstjqam-PRDz=&-BVb^xZe>{C{$cza!_sV&3M*l0ocMJVr!l~TlJi4JChDn9Nn zc&la1caY}0P&Ho=r;)l;mKBf$V<6A*R6XC}s98g%I7ZIAFI=e6SqQ4;oevw)nw0%^ zKq9#$;{3R0zJv}#mr7@}e+5-(`{C?^vEE#xb7uBY=X#_1v+@~@l?W@Zaq+Yo9bpu& zR<0us_T`(Q6qp1xYb)Rq;tJ|aTZ&y5xqx<_j-|>1$SEi@3!A|| z9YH<3ub_#ai=2WG_V9iQ!NU8mB|$4ZK3Gr>_s15;6W-XV-*##3TjwoMP&yb zq!L{!sQoUn<_ZWb)BbzloM2Zs1tb=+FBn*$!EQmp3Ml#oe;g0);^XP&_osni`NR1A z0SL>FG{F)8;h%d#4-g0eK+%&0UD-=ghUr~yDQ?!lNE5tKiJ_rjY{@`Q1vjbVAFU;|?Qs;w|1hFx_ z`*jR7rVAU>9*yRSpD1)#aOb!)@ak(5hk;guG$_9)=K8Ie^uOP<63|FjrX2UEcJw07 zD5c?bxHD${?)1+CMgPg@0|kH>4NzJZO*;#rl-xA_8*SHCS}ygKZP7*uHbRtmaTE%n zp7Vt7QIt|IIN?)fyS#8IxKHO$?TeY{DpQl5^kyAd$HH^Aa)SJC+I0!ULR znF7*z6R6~{CCW6M^qKuU!N`I`>YB3i6toA7f7#3%T&$5&wm0nY{&d9(g)LB$%g9dX zf>HfjVn9;)rG-^=)tiGDd<5M4wDHPl@yEGU_whSh78l$%S*WCqjvj^Xt?_VKp0T{pQGU!F;?_^4EMT$__$E zH0hMGQlo@W2p^_tPZsnirl@pGb<#0a^*g5ihYtSzKKx%Wg;i4h8B_c6Z+PPWM!I%g zOr-dLp|0@RV@@&InVrwRJfPT~ZY840gT$Jl4)HP^qcTUWE~1&}C2wS3Sv9pJWiRva zyK}a9ilnrYe7SB$bu~GF&GM`D1h@ukNsJY|Yt>|?q(4gzgSUuGwSIfsmlD)%J2V0@ zTU&-58&x%P)-#Oev2~&}bv^wwRbD$?Enu(jJiuwM3shGOZ{$juY+RGk#m^`!p7+vO zAjWFn1{dq`T?N^TggHmN3~VGf^5?a_)R-cj5yfk-?V<|S)%uKn{YGL)7(~eAhWA56 zj7ZS7amp#qQM;t>%6F)v{1S-Gq>88IPiL?2X9=q_r$vhc4{Pd3$WssBMbZaV2W zu&8||{U99-3!x+JudoA1KSAx^0qg$*YLr)FKtJ($lC@k)W?khPY!~B&3F~Xnxs_WH)b*(MC{~@>r={U4@A6+2p8il>0lojdT`r8~C>rA6;jw^lZK9gk<_y!v za(Rbclc{1;TFBtT`lr|YO0}|UXzh>FLsx6RQUq8=?V4{NR#=oxL2}kHb-ZAfuN Date: Wed, 20 Nov 2024 17:41:36 -0500 Subject: [PATCH 020/122] Fixes imports Signed-off-by: Darshit Chanpura --- .../java/org/opensearch/security/OpenSearchSecurityPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 96aa7c2bf6..1931486eb8 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -43,6 +43,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -279,7 +280,6 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile OpensearchDynamicSetting transportPassiveAuthSetting; private volatile PasswordHasher passwordHasher; private volatile DlsFlsBaseContext dlsFlsBaseContext; - private ResourceAccessEvaluator resourceAccessEvaluator; private ResourceManagementRepository rmr; private ResourceAccessHandler resourceAccessHandler; private final Set indicesToListen = new HashSet<>(); From 57661e7d00a1b249526d47d38809cb08ac3dd757 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Nov 2024 11:16:19 -0500 Subject: [PATCH 021/122] Cleans up create action Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePlugin.java | 4 --- .../CreateResourceTransportAction.java | 32 ++++++------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 74a8378887..6ba4b82b4a 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -12,7 +12,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; @@ -70,9 +69,6 @@ public class SampleResourcePlugin extends Plugin implements ActionPlugin, System private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; - - public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); - private Client client; @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index 8bff7b44a3..53e251c5b6 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -9,15 +9,10 @@ package org.opensearch.sample.transport; import java.io.IOException; -import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.ActionFilters; @@ -28,9 +23,8 @@ import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.sample.Resource; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.SampleResourceScope; import org.opensearch.sample.actions.create.CreateResourceAction; import org.opensearch.sample.actions.create.CreateResourceRequest; import org.opensearch.sample.actions.create.CreateResourceResponse; @@ -58,7 +52,8 @@ public CreateResourceTransportAction(TransportService transportService, ActionFi @Override protected void doExecute(Task task, CreateResourceRequest request, ActionListener listener) { - try (ThreadContext.StoredContext ignore = transportService.getThreadPool().getThreadContext().stashContext()) { + ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); + try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { createResource(request, listener); listener.onResponse(new CreateResourceResponse("Resource " + request.getResource() + " created successfully.")); } catch (Exception e) { @@ -69,31 +64,22 @@ protected void doExecute(Task task, CreateResourceRequest request, ActionListene private void createResource(CreateResourceRequest request, ActionListener listener) { Resource sample = request.getResource(); - try { + try (XContentBuilder builder = jsonBuilder()) { IndexRequest ir = nodeClient.prepareIndex(RESOURCE_INDEX_NAME) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .setSource(sample.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .setSource(sample.toXContent(builder, ToXContent.EMPTY_PARAMS)) .request(); - log.warn("Index Request: {}", ir.toString()); + log.info("Index Request: {}", ir.toString()); - ActionListener irListener = getIndexResponseActionListener(listener); - nodeClient.index(ir, irListener); + nodeClient.index(ir, getIndexResponseActionListener(listener)); } catch (IOException e) { - throw new RuntimeException(e); + listener.onFailure(new RuntimeException(e)); } } private static ActionListener getIndexResponseActionListener(ActionListener listener) { - SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(List.of(), List.of(), List.of()); - SharedWithScope sharedWithScope = new SharedWithScope(SampleResourceScope.SAMPLE_FULL_ACCESS.getName(), sharedWithPerScope); - ShareWith shareWith = new ShareWith(List.of(sharedWithScope)); - return ActionListener.wrap(idxResponse -> { - log.info("Created resource: {}", idxResponse.toString()); - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - ResourceSharing sharing = rs.getResourceAccessControlPlugin().shareWith(idxResponse.getId(), idxResponse.getIndex(), shareWith); - log.info("Created resource sharing entry: {}", sharing.toString()); - }, listener::onFailure); + return ActionListener.wrap(idxResponse -> { log.info("Created resource: {}", idxResponse.toString()); }, listener::onFailure); } } From a30be5779b0c285a7b1cc6e654b77893c2c19896 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Nov 2024 14:18:16 -0500 Subject: [PATCH 022/122] Adds concrete implementations of remainder methods Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 8 +- .../security/auth/BackendRegistry.java | 4 + .../security/filter/SecurityFilter.java | 1 + .../resources/ResourceAccessHandler.java | 36 +- .../ResourceManagementRepository.java | 37 +- .../ResourceSharingIndexHandler.java | 907 +++++++++++++++++- .../ResourceSharingIndexListener.java | 40 +- 7 files changed, 930 insertions(+), 103 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 1931486eb8..ccee464e01 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -726,7 +726,9 @@ public void onIndexModule(IndexModule indexModule) { log.info("Indices to listen to: {}", this.indicesToListen); if (this.indicesToListen.contains(indexModule.getIndex().getName())) { - indexModule.addIndexOperationListener(ResourceSharingIndexListener.getInstance()); + ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance(); + resourceSharingIndexListener.initialize(threadPool, localClient); + indexModule.addIndexOperationListener(resourceSharingIndexListener); log.warn("Security plugin started listening to operations on index {}", indexModule.getIndex().getName()); } @@ -1205,7 +1207,7 @@ public Collection createComponents( // NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires // security index to be accessible which means - // communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence + // communication with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence // the base values from opensearch.yml // is used to first establish trust between same cluster nodes and there after dynamic config is loaded if enabled. if (DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { @@ -1217,7 +1219,7 @@ public Collection createComponents( ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler(resourceSharingIndex, localClient, threadPool); resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); - rmr = ResourceManagementRepository.create(settings, threadPool, localClient, rsIndexHandler); + rmr = ResourceManagementRepository.create(rsIndexHandler); components.add(adminDns); components.add(cr); diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 0b00bcf943..eb9bb504fd 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -224,6 +224,7 @@ public boolean authenticate(final SecurityRequestChannel request) { if (adminDns.isAdminDN(sslPrincipal)) { // PKI authenticated REST call threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal)); + threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal)); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; } @@ -389,6 +390,8 @@ public boolean authenticate(final SecurityRequestChannel request) { final User impersonatedUser = impersonate(request, authenticatedUser); threadPool.getThreadContext() .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser == null ? authenticatedUser : impersonatedUser); + threadPool.getThreadContext() + .putPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser == null ? authenticatedUser : impersonatedUser); auditLog.logSucceededLogin( (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), false, @@ -422,6 +425,7 @@ public boolean authenticate(final SecurityRequestChannel request) { anonymousUser.setRequestedTenant(tenant); threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); auditLog.logSucceededLogin(anonymousUser.getName(), false, null, request); if (isDebugEnabled) { log.debug("Anonymous User is authenticated"); diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 3323c9e38a..b2ede030a7 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -345,6 +345,7 @@ private void ap log.info("Transport auth in passive mode and no user found. Injecting default user"); user = User.DEFAULT_TRANSPORT_USER; threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER, user); } else { log.error( "No user found for " diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 32fa077e71..d5e79a1fdf 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -49,41 +49,41 @@ public ResourceAccessHandler( this.adminDNs = adminDns; } - public List listAccessibleResourcesInPlugin(String systemIndex) { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + public List listAccessibleResourcesInPlugin(String pluginIndex) { + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); return Collections.emptyList(); } - LOGGER.info("Listing accessible resource within a system index {} for : {}", systemIndex, user.getName()); + LOGGER.info("Listing accessible resource within a system index {} for : {}", pluginIndex, user.getName()); - // TODO check if user is admin, if yes all resources should be accessible + // check if user is admin, if yes all resources should be accessible if (adminDNs.isAdmin(user)) { - return loadAllResources(systemIndex); + return loadAllResources(pluginIndex); } Set result = new HashSet<>(); // 0. Own resources - result.addAll(loadOwnResources(systemIndex, user.getName())); + result.addAll(loadOwnResources(pluginIndex, user.getName())); // 1. By username - result.addAll(loadSharedWithResources(systemIndex, Set.of(user.getName()), "users")); + result.addAll(loadSharedWithResources(pluginIndex, Set.of(user.getName()), EntityType.USERS.toString())); // 2. By roles Set roles = user.getSecurityRoles(); - result.addAll(loadSharedWithResources(systemIndex, roles, "roles")); + result.addAll(loadSharedWithResources(pluginIndex, roles, EntityType.ROLES.toString())); // 3. By backend_roles Set backendRoles = user.getRoles(); - result.addAll(loadSharedWithResources(systemIndex, backendRoles, "backend_roles")); + result.addAll(loadSharedWithResources(pluginIndex, backendRoles, EntityType.BACKEND_ROLES.toString())); return result.stream().toList(); } public boolean hasPermission(String resourceId, String systemIndexName, String scope) { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); Set userRoles = user.getSecurityRoles(); @@ -109,24 +109,22 @@ public boolean hasPermission(String resourceId, String systemIndexName, String s } public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith.toString()); - // TODO fix this to fetch user-name correctly, need to hydrate user context since context might have been stashed. - // (persistentHeader?) - CreatedBy createdBy = new CreatedBy("", ""); + CreatedBy createdBy = new CreatedBy(user.getName()); return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, createdBy, shareWith); } public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess); return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess); } public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, systemIndexName, user.getName()); ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId); @@ -142,7 +140,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String systemIndex } public boolean deleteAllResourceSharingRecordsForCurrentUser() { - final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName()); @@ -159,8 +157,8 @@ private List loadOwnResources(String systemIndex, String username) { return this.resourceSharingIndexHandler.fetchDocumentsByField(systemIndex, "created_by.user", username); } - private List loadSharedWithResources(String systemIndex, Set accessWays, String shareWithType) { - return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(systemIndex, accessWays, shareWithType); + private List loadSharedWithResources(String systemIndex, Set entities, String shareWithType) { + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(systemIndex, entities, shareWithType); } private boolean isOwnerOfResource(ResourceSharing document, String userName) { diff --git a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java index da3678728d..84749153f5 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java +++ b/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java @@ -11,44 +11,25 @@ package org.opensearch.security.resources; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.client.Client; -import org.opensearch.common.settings.Settings; -import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.threadpool.ThreadPool; - public class ResourceManagementRepository { - private static final Logger LOGGER = LogManager.getLogger(ConfigurationRepository.class); - - private final Client client; - - private final ThreadPool threadPool; - private final ResourceSharingIndexHandler resourceSharingIndexHandler; - protected ResourceManagementRepository( - final ThreadPool threadPool, - final Client client, - final ResourceSharingIndexHandler resourceSharingIndexHandler - ) { - this.client = client; - this.threadPool = threadPool; + protected ResourceManagementRepository(final ResourceSharingIndexHandler resourceSharingIndexHandler) { this.resourceSharingIndexHandler = resourceSharingIndexHandler; } - public static ResourceManagementRepository create( - Settings settings, - final ThreadPool threadPool, - Client client, - ResourceSharingIndexHandler resourceSharingIndexHandler - ) { + public static ResourceManagementRepository create(ResourceSharingIndexHandler resourceSharingIndexHandler) { - return new ResourceManagementRepository(threadPool, client, resourceSharingIndexHandler); + return new ResourceManagementRepository(resourceSharingIndexHandler); } + /** + * Creates the resource sharing index if it doesn't already exist. + * This method is called during the initialization phase of the repository. + * It ensures that the index is set up with the necessary mappings and settings + * before any operations are performed on the index. + */ public void createResourceSharingIndexIfAbsent() { // TODO check if this should be wrapped in an atomic completable future diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index b175ad53d0..5568ee06d6 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -10,35 +10,44 @@ package org.opensearch.security.resources; import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.lucene.search.join.ScoreMode; -import org.opensearch.accesscontrol.resources.CreatedBy; -import org.opensearch.accesscontrol.resources.EntityType; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.*; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.search.*; import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; +import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.reindex.*; +import org.opensearch.script.Script; +import org.opensearch.script.ScriptType; +import org.opensearch.search.Scroll; +import org.opensearch.search.SearchHit; +import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.threadpool.ThreadPool; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; public class ResourceSharingIndexHandler { - private final static int MINIMUM_HASH_BITS = 128; - private static final Logger LOGGER = LogManager.getLogger(ResourceSharingIndexHandler.class); private final Client client; @@ -55,6 +64,25 @@ public ResourceSharingIndexHandler(final String indexName, final Client client, public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); + /** + * Creates the resource sharing index if it doesn't already exist. + * This method initializes the index with predefined mappings and settings + * for storing resource sharing information. + * The index will be created with the following structure: + * - source_idx (keyword): The source index containing the original document + * - resource_id (keyword): The ID of the shared resource + * - created_by (object): Information about the user who created the sharing + * - user (keyword): Username of the creator + * - share_with (object): Access control configuration for shared resources + * - [group_name] (object): Name of the access group + * - users (array): List of users with access + * - roles (array): List of roles with access + * - backend_roles (array): List of backend roles with access + * + * @throws RuntimeException if there are issues reading/writing index settings + * or communicating with the cluster + */ + public void createResourceSharingIndexIfAbsent(Callable callable) { // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { @@ -75,7 +103,29 @@ public void createResourceSharingIndexIfAbsent(Callable callable) { } } - public boolean indexResourceSharing(String resourceId, String resourceIndex, CreatedBy createdBy, ShareWith shareWith) + /** + * Creates or updates a resource sharing record in the dedicated resource sharing index. + * This method handles the persistence of sharing metadata for resources, including + * the creator information and sharing permissions. + * + * @param resourceId The unique identifier of the resource being shared + * @param resourceIndex The source index where the original resource is stored + * @param createdBy Object containing information about the user creating/updating the sharing + * @param shareWith Object containing the sharing permissions' configuration. Can be null for initial creation. + * When provided, it should contain the access control settings for different groups: + * { + * "group_name": { + * "users": ["user1", "user2"], + * "roles": ["role1", "role2"], + * "backend_roles": ["backend_role1"] + * } + * } + * + * @return ResourceSharing Returns resourceSharing object if the operation was successful, null otherwise + * @throws IOException if there are issues with index operations or JSON processing + */ + + public ResourceSharing indexResourceSharing(String resourceId, String resourceIndex, CreatedBy createdBy, ShareWith shareWith) throws IOException { try { @@ -88,58 +138,839 @@ public boolean indexResourceSharing(String resourceId, String resourceIndex, Cre LOGGER.info("Index Request: {}", ir.toString()); - ActionListener irListener = ActionListener.wrap( - idxResponse -> { LOGGER.info("Created {} entry.", resourceSharingIndex); }, - (failResponse) -> { - LOGGER.error(failResponse.getMessage()); - LOGGER.info("Failed to create {} entry.", resourceSharingIndex); - } - ); + ActionListener irListener = ActionListener.wrap(idxResponse -> { + LOGGER.info("Successfully created {} entry.", resourceSharingIndex); + }, (failResponse) -> { + LOGGER.error(failResponse.getMessage()); + LOGGER.info("Failed to create {} entry.", resourceSharingIndex); + }); client.index(ir, irListener); + return entry; } catch (Exception e) { LOGGER.info("Failed to create {} entry.", resourceSharingIndex, e); - return false; + return null; } - return true; } - public List fetchDocumentsByField(String systemIndex, String field, String value) { - LOGGER.info("Fetching documents from index: {}, where {} = {}", systemIndex, field, value); + /** + * Fetches all resource sharing records that match the specified system index. This method retrieves + * a list of resource IDs associated with the given system index from the resource sharing index. + * + *

The method executes the following steps: + *

    + *
  1. Creates a search request with term query matching the system index
  2. + *
  3. Applies source filtering to only fetch resource_id field
  4. + *
  5. Executes the search with a limit of 10000 documents
  6. + *
  7. Processes the results to extract resource IDs
  8. + *
+ * + *

Example query structure: + *

+        * {
+        *   "query": {
+        *     "term": {
+        *       "source_idx": "system_index_name"
+        *     }
+        *   },
+        *   "_source": ["resource_id"],
+        *   "size": 10000
+        * }
+        * 
+ * + * @param pluginIndex The source index to match against the source_idx field + * @return List containing resource IDs that belong to the specified system index. + * Returns an empty list if: + *
    + *
  • No matching documents are found
  • + *
  • An error occurs during the search operation
  • + *
  • The system index parameter is invalid
  • + *
+ * + * @apiNote This method: + *
    + *
  • Uses source filtering for optimal performance
  • + *
  • Performs exact matching on the source_idx field
  • + *
  • Returns an empty list instead of throwing exceptions
  • + *
+ */ + public List fetchAllDocuments(String pluginIndex) { + LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); + + try { + SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + searchSourceBuilder.query(QueryBuilders.termQuery("source_idx", pluginIndex)); + searchSourceBuilder.size(10000); // TODO check what size should be set here. + + searchSourceBuilder.fetchSource(new String[] { "resource_id" }, null); + + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + + List resourceIds = new ArrayList<>(); - return List.of(); + SearchHit[] hits = searchResponse.getHits().getHits(); + for (SearchHit hit : hits) { + Map sourceAsMap = hit.getSourceAsMap(); + if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { + resourceIds.add(sourceAsMap.get("resource_id").toString()); + } + } + + LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); + + return resourceIds; + + } catch (Exception e) { + LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); + return List.of(); + } } - public List fetchAllDocuments(String systemIndex) { - LOGGER.info("Fetching all documents from index: {}", systemIndex); - return List.of(); + /** + * Fetches documents that match the specified system index and have specific access type values. + * This method uses scroll API to handle large result sets efficiently. + * + *

The method executes the following steps: + *

    + *
  1. Validates the entityType parameter
  2. + *
  3. Creates a scrolling search request with a compound query
  4. + *
  5. Processes results in batches using scroll API
  6. + *
  7. Collects all matching resource IDs
  8. + *
  9. Cleans up scroll context
  10. + *
+ * + *

Example query structure: + *

+    * {
+    *   "query": {
+    *     "bool": {
+    *       "must": [
+    *         { "term": { "source_idx": "system_index_name" } },
+    *         {
+    *           "bool": {
+    *             "should": [
+    *               {
+    *                 "nested": {
+    *                   "path": "share_with.*.entityType",
+    *                   "query": {
+    *                     "term": { "share_with.*.entityType": "entity_value" }
+    *                   }
+    *                 }
+    *               }
+    *             ],
+    *             "minimum_should_match": 1
+    *           }
+    *         }
+    *       ]
+    *     }
+    *   },
+    *   "_source": ["resource_id"],
+    *   "size": 1000
+    * }
+    * 
+ * + * @param pluginIndex The source index to match against the source_idx field + * @param entities Set of values to match in the specified entityType field + * @param entityType The type of association with the resource. Must be one of: + *
    + *
  • "users" - for user-based access
  • + *
  • "roles" - for role-based access
  • + *
  • "backend_roles" - for backend role-based access
  • + *
+ * @return List List of resource IDs that match the criteria. The list may be empty + * if no matches are found + * + * @throws RuntimeException if the search operation fails + * + * @apiNote This method: + *
    + *
  • Uses scroll API with 1-minute timeout
  • + *
  • Processes results in batches of 1000 documents
  • + *
  • Performs source filtering for optimization
  • + *
  • Uses nested queries for accessing array elements
  • + *
  • Properly cleans up scroll context after use
  • + *
+ */ + + public List fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType) { + LOGGER.debug("Fetching documents from index: {}, where share_with.*.{} contains any of {}", pluginIndex, entityType, entities); + + List resourceIds = new ArrayList<>(); + final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); + + try { + SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); + searchRequest.scroll(scroll); + + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("source_idx", pluginIndex)); + + BoolQueryBuilder shouldQuery = QueryBuilders.boolQuery(); + for (String entity : entities) { + shouldQuery.should( + QueryBuilders.nestedQuery( + "share_with.*." + entityType, + QueryBuilders.termQuery("share_with.*." + entityType, entity), + ScoreMode.None + ) + ); + } + shouldQuery.minimumShouldMatch(1); + boolQuery.must(shouldQuery); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) + .size(1000) + .fetchSource(new String[] { "resource_id" }, null); + + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + String scrollId = searchResponse.getScrollId(); + SearchHit[] hits = searchResponse.getHits().getHits(); + + while (hits != null && hits.length > 0) { + for (SearchHit hit : hits) { + Map sourceAsMap = hit.getSourceAsMap(); + if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { + resourceIds.add(sourceAsMap.get("resource_id").toString()); + } + } + + SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); + scrollRequest.scroll(scroll); + searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); + scrollId = searchResponse.getScrollId(); + hits = searchResponse.getHits().getHits(); + } + + ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); + clearScrollRequest.addScrollId(scrollId); + client.clearScroll(clearScrollRequest).actionGet(); + + LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); + + return resourceIds; + + } catch (Exception e) { + LOGGER.error( + "Failed to fetch documents from {} for criteria - systemIndex: {}, shareWithType: {}, accessWays: {}", + resourceSharingIndex, + pluginIndex, + entityType, + entities, + e + ); + throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); + } } - public List fetchDocumentsForAllScopes(String systemIndex, Set accessWays, String shareWithType) { - return List.of(); + /** + * Fetches documents from the resource sharing index that match a specific field value. + * This method uses scroll API to efficiently handle large result sets and performs exact + * matching on both system index and the specified field. + * + *

The method executes the following steps: + *

    + *
  1. Validates input parameters for null/empty values
  2. + *
  3. Creates a scrolling search request with a bool query
  4. + *
  5. Processes results in batches using scroll API
  6. + *
  7. Extracts resource IDs from matching documents
  8. + *
  9. Cleans up scroll context after completion
  10. + *
+ * + *

Example query structure: + *

+     * {
+     *   "query": {
+     *     "bool": {
+     *       "must": [
+     *         { "term": { "source_idx": "system_index_value" } },
+     *         { "term": { "field_name": "field_value" } }
+     *       ]
+     *     }
+     *   },
+     *   "_source": ["resource_id"],
+     *   "size": 1000
+     * }
+     * 
+ * + * @param systemIndex The source index to match against the source_idx field + * @param field The field name to search in. Must be a valid field in the index mapping + * @param value The value to match for the specified field. Performs exact term matching + * @return List List of resource IDs that match the criteria. Returns an empty list + * if no matches are found + * + * @throws IllegalArgumentException if any parameter is null or empty + * @throws RuntimeException if the search operation fails, wrapping the underlying exception + * + * @apiNote This method: + *
    + *
  • Uses scroll API with 1-minute timeout for handling large result sets
  • + *
  • Performs exact term matching (not analyzed) on field values
  • + *
  • Processes results in batches of 1000 documents
  • + *
  • Uses source filtering to only fetch resource_id field
  • + *
  • Automatically cleans up scroll context after use
  • + *
+ * + * Example usage: + *
+     * List resources = fetchDocumentsByField("myIndex", "status", "active");
+     * 
+ */ + + public List fetchDocumentsByField(String systemIndex, String field, String value) { + if (StringUtils.isBlank(systemIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { + throw new IllegalArgumentException("systemIndex, field, and value must not be null or empty"); + } + + LOGGER.debug("Fetching documents from index: {}, where {} = {}", systemIndex, field, value); + + List resourceIds = new ArrayList<>(); + final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); + + try { + // Create initial search request + SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); + searchRequest.scroll(scroll); + + // Build the query + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("source_idx", systemIndex)) + .must(QueryBuilders.termQuery(field, value)); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) + .size(1000) + .fetchSource(new String[] { "resource_id" }, null); + + searchRequest.source(searchSourceBuilder); + + // Execute initial search + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + String scrollId = searchResponse.getScrollId(); + SearchHit[] hits = searchResponse.getHits().getHits(); + + // Process results in batches + while (hits != null && hits.length > 0) { + for (SearchHit hit : hits) { + Map sourceAsMap = hit.getSourceAsMap(); + if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { + resourceIds.add(sourceAsMap.get("resource_id").toString()); + } + } + + SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); + scrollRequest.scroll(scroll); + searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); + scrollId = searchResponse.getScrollId(); + hits = searchResponse.getHits().getHits(); + } + + // Clear scroll + ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); + clearScrollRequest.addScrollId(scrollId); + client.clearScroll(clearScrollRequest).actionGet(); + + LOGGER.debug("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); + + return resourceIds; + + } catch (Exception e) { + LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); + throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); + } } - public ResourceSharing fetchDocumentById(String systemIndexName, String resourceId) { - return null; + /** + * Fetches a specific resource sharing document by its resource ID and system index. + * This method performs an exact match search and parses the result into a ResourceSharing object. + * + *

The method executes the following steps: + *

    + *
  1. Validates input parameters for null/empty values
  2. + *
  3. Creates a search request with a bool query for exact matching
  4. + *
  5. Executes the search with a limit of 1 document
  6. + *
  7. Parses the result using XContent parser if found
  8. + *
  9. Returns null if no matching document exists
  10. + *
+ * + *

Example query structure: + *

+    * {
+    *   "query": {
+    *     "bool": {
+    *       "must": [
+    *         { "term": { "source_idx": "system_index_name" } },
+    *         { "term": { "resource_id": "resource_id_value" } }
+    *       ]
+    *     }
+    *   },
+    *   "size": 1
+    * }
+    * 
+ * + * @param pluginIndex The source index to match against the source_idx field + * @param resourceId The resource ID to fetch. Must exactly match the resource_id field + * @return ResourceSharing object if a matching document is found, null if no document + * matches the criteria + * + * @throws IllegalArgumentException if systemIndexName or resourceId is null or empty + * @throws RuntimeException if the search operation fails or parsing errors occur, + * wrapping the underlying exception + * + * @apiNote This method: + *
    + *
  • Uses term queries for exact matching
  • + *
  • Expects only one matching document per resource ID
  • + *
  • Uses XContent parsing for consistent object creation
  • + *
  • Returns null instead of throwing exceptions for non-existent documents
  • + *
  • Provides detailed logging for troubleshooting
  • + *
+ * + * Example usage: + *
+    * ResourceSharing sharing = fetchDocumentById("myIndex", "resource123");
+    * if (sharing != null) {
+    *     // Process the resource sharing object
+    * }
+    * 
+ */ + + public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) { + // Input validation + if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(resourceId)) { + throw new IllegalArgumentException("systemIndexName and resourceId must not be null or empty"); + } + + LOGGER.debug("Fetching document from index: {}, with resourceId: {}", pluginIndex, resourceId); + + try { + SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); + + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("source_idx", pluginIndex)) + .must(QueryBuilders.termQuery("resource_id", resourceId)); + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery).size(1); // We only need one document since + // a resource must have only one + // sharing entry + + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + + SearchHit[] hits = searchResponse.getHits().getHits(); + if (hits.length == 0) { + LOGGER.debug("No document found for resourceId: {} in index: {}", resourceId, pluginIndex); + return null; + } + + SearchHit hit = hits[0]; + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, hit.getSourceAsString()) + ) { + + parser.nextToken(); + + ResourceSharing resourceSharing = ResourceSharing.fromXContent(parser); + + LOGGER.debug("Successfully fetched document for resourceId: {} from index: {}", resourceId, pluginIndex); + + return resourceSharing; + } + + } catch (Exception e) { + LOGGER.error("Failed to fetch document for resourceId: {} from index: {}", resourceId, pluginIndex, e); + throw new RuntimeException("Failed to fetch document: " + e.getMessage(), e); + } } - public ResourceSharing updateResourceSharingInfo(String resourceId, String systemIndexName, CreatedBy createdBy, ShareWith shareWith) { + /** + * Updates resource sharing entries that match the specified source index and resource ID + * using the provided update script. This method performs an update-by-query operation + * in the resource sharing index. + * + *

The method executes the following steps: + *

    + *
  1. Creates a bool query to match exact source index and resource ID
  2. + *
  3. Constructs an update-by-query request with the query and update script
  4. + *
  5. Executes the update operation
  6. + *
  7. Returns success/failure status based on update results
  8. + *
+ * + *

Example document matching structure: + *

+     * {
+     *   "source_idx": "source_index_name",
+     *   "resource_id": "resource_id_value",
+     *   "share_with": {
+     *     // sharing configuration to be updated
+     *   }
+     * }
+     * 
+ * + * @param sourceIdx The source index to match in the query (exact match) + * @param resourceId The resource ID to match in the query (exact match) + * @param updateScript The script containing the update operations to be performed. + * This script defines how the matching documents should be modified + * @return boolean true if at least one document was updated, false if no documents + * were found or update failed + * + * @apiNote This method: + *
    + *
  • Uses term queries for exact matching of source_idx and resource_id
  • + *
  • Returns false for both "no matching documents" and "operation failure" cases
  • + *
  • Logs the complete update request for debugging purposes
  • + *
  • Provides detailed logging for success and failure scenarios
  • + *
+ * + * @implNote The update operation uses a bool query with two must clauses: + *
+     * {
+     *   "query": {
+     *     "bool": {
+     *       "must": [
+     *         { "term": { "source_idx": sourceIdx } },
+     *         { "term": { "resource_id": resourceId } }
+     *       ]
+     *     }
+     *   }
+     * }
+     * 
+ */ + private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId, Script updateScript) { try { - boolean success = indexResourceSharing(resourceId, systemIndexName, createdBy, shareWith); - return success ? new ResourceSharing(resourceId, systemIndexName, createdBy, shareWith) : null; - } catch (IOException e) { - throw new RuntimeException(e); + // Create a bool query to match both fields + BoolQueryBuilder query = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("source_idx", sourceIdx)) + .must(QueryBuilders.termQuery("resource_id", resourceId)); + + UpdateByQueryRequest ubq = new UpdateByQueryRequest(resourceSharingIndex).setQuery(query).setScript(updateScript); + + LOGGER.info("Update By Query Request: {}", ubq.toString()); + + BulkByScrollResponse response = client.execute(UpdateByQueryAction.INSTANCE, ubq).actionGet(); + + if (response.getUpdated() > 0) { + LOGGER.info("Successfully updated {} documents in {}.", response.getUpdated(), resourceSharingIndex); + return true; + } else { + LOGGER.info( + "No documents found to update in {} for source_idx: {} and resource_id: {}", + resourceSharingIndex, + sourceIdx, + resourceId + ); + return false; + } + + } catch (Exception e) { + LOGGER.error("Failed to update documents in {}.", resourceSharingIndex, e); + return false; } } + /** + * Updates the sharing configuration for an existing resource in the resource sharing index. + * This method modifies the sharing permissions for a specific resource identified by its + * resource ID and source index. + * + * @param resourceId The unique identifier of the resource whose sharing configuration needs to be updated + * @param sourceIdx The source index where the original resource is stored + * @param shareWith Updated sharing configuration object containing access control settings: + * { + * "scope": { + * "users": ["user1", "user2"], + * "roles": ["role1", "role2"], + * "backend_roles": ["backend_role1"] + * } + * } + * @return ResourceSharing Returns resourceSharing object if the update was successful, null otherwise + * @throws RuntimeException if there's an error during the update operation + */ + public ResourceSharing updateResourceSharingInfo(String resourceId, String sourceIdx, CreatedBy createdBy, ShareWith shareWith) { + Script updateScript = new Script( + ScriptType.INLINE, + "painless", + "ctx._source.shareWith = params.newShareWith", + Collections.singletonMap("newShareWith", shareWith) + ); + + boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); + return success ? new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith) : null; + } + + /** + * Revokes access for specified entities from a resource sharing document. This method removes the specified + * entities (users, roles, or backend roles) from the existing sharing configuration while preserving other + * sharing settings. + * + *

The method performs the following steps: + *

    + *
  1. Fetches the existing document
  2. + *
  3. Removes specified entities from their respective lists in all sharing groups
  4. + *
  5. Updates the document if modifications were made
  6. + *
  7. Returns the updated resource sharing configuration
  8. + *
+ * + *

Example document structure: + *

+     * {
+     *   "source_idx": "system_index_name",
+     *   "resource_id": "resource_id",
+     *   "share_with": {
+     *     "group_name": {
+     *       "users": ["user1", "user2"],
+     *       "roles": ["role1", "role2"],
+     *       "backend_roles": ["backend_role1"]
+     *     }
+     *   }
+     * }
+     * 
+ * + * @param resourceId The ID of the resource from which to revoke access + * @param systemIndexName The name of the system index where the resource exists + * @param revokeAccess A map containing entity types (USER, ROLE, BACKEND_ROLE) and their corresponding + * values to be removed from the sharing configuration + * @return The updated ResourceSharing object after revoking access, or null if the document doesn't exist + * @throws IllegalArgumentException if resourceId, systemIndexName is null/empty, or if revokeAccess is null/empty + * @throws RuntimeException if the update operation fails or encounters an error + * + * @see EntityType + * @see ResourceSharing + * + * @apiNote This method modifies the existing document. If no modifications are needed (i.e., specified + * entities don't exist in the current configuration), the original document is returned unchanged. + * @example + *
+     * Map> revokeAccess = new HashMap<>();
+     * revokeAccess.put(EntityType.USER, Arrays.asList("user1", "user2"));
+     * revokeAccess.put(EntityType.ROLE, Arrays.asList("role1"));
+     * ResourceSharing updated = revokeAccess("resourceId", "systemIndex", revokeAccess);
+     * 
+ */ + public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { - return null; + // TODO; check if this needs to be done per scope rather than for all scopes + + // Input validation + if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(systemIndexName) || revokeAccess == null || revokeAccess.isEmpty()) { + throw new IllegalArgumentException("resourceId, systemIndexName, and revokeAccess must not be null or empty"); + } + + LOGGER.debug("Revoking access for resource {} in {} for entities: {}", resourceId, systemIndexName, revokeAccess); + + try { + // First fetch the existing document + ResourceSharing existingResource = fetchDocumentById(systemIndexName, resourceId); + if (existingResource == null) { + LOGGER.warn("No document found for resourceId: {} in index: {}", resourceId, systemIndexName); + return null; + } + + ShareWith shareWith = existingResource.getShareWith(); + boolean modified = false; + + if (shareWith != null) { + for (SharedWithScope sharedWithScope : shareWith.getSharedWithScopes()) { + SharedWithScope.SharedWithPerScope sharedWithPerScope = sharedWithScope.getSharedWithPerScope(); + + for (Map.Entry> entry : revokeAccess.entrySet()) { + EntityType entityType = entry.getKey(); + List entities = entry.getValue(); + + // Check if the entity type exists in the share_with configuration + switch (entityType) { + case USERS: + if (sharedWithPerScope.getUsers() != null) { + modified = sharedWithPerScope.getUsers().removeAll(entities) || modified; + } + break; + case ROLES: + if (sharedWithPerScope.getRoles() != null) { + modified = sharedWithPerScope.getRoles().removeAll(entities) || modified; + } + break; + case BACKEND_ROLES: + if (sharedWithPerScope.getBackendRoles() != null) { + modified = sharedWithPerScope.getBackendRoles().removeAll(entities) || modified; + } + break; + } + } + } + } + + if (!modified) { + LOGGER.debug("No modifications needed for resource: {}", resourceId); + return existingResource; + } + + // Update resource sharing info + return updateResourceSharingInfo(resourceId, systemIndexName, existingResource.getCreatedBy(), shareWith); + + } catch (Exception e) { + LOGGER.error("Failed to revoke access for resource: {} in index: {}", resourceId, systemIndexName, e); + throw new RuntimeException("Failed to revoke access: " + e.getMessage(), e); + } } - public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { - return false; + /** + * Deletes resource sharing records that match the specified source index and resource ID. + * This method performs a delete-by-query operation in the resource sharing index. + * + *

The method executes the following steps: + *

    + *
  1. Creates a delete-by-query request with a bool query
  2. + *
  3. Matches documents based on exact source index and resource ID
  4. + *
  5. Executes the delete operation with immediate refresh
  6. + *
  7. Returns the success/failure status based on deletion results
  8. + *
+ * + *

Example document structure that will be deleted: + *

+     * {
+     *   "source_idx": "source_index_name",
+     *   "resource_id": "resource_id_value",
+     *   "share_with": {
+     *     // sharing configuration
+     *   }
+     * }
+     * 
+ * + * @param sourceIdx The source index to match in the query (exact match) + * @param resourceId The resource ID to match in the query (exact match) + * @return boolean true if at least one document was deleted, false if no documents were found or deletion failed + * + * @implNote The delete operation uses a bool query with two must clauses to ensure exact matching: + *
+     * {
+     *   "query": {
+     *     "bool": {
+     *       "must": [
+     *         { "term": { "source_idx": sourceIdx } },
+     *         { "term": { "resource_id": resourceId } }
+     *       ]
+     *     }
+     *   }
+     * }
+     * 
+ */ + public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) { + LOGGER.info("Deleting documents from {} where source_idx = {} and resource_id = {}", resourceSharingIndex, sourceIdx, resourceId); + + try { + DeleteByQueryRequest dbq = new DeleteByQueryRequest(resourceSharingIndex).setQuery( + QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("source_idx", sourceIdx)) + .must(QueryBuilders.termQuery("resource_id", resourceId)) + ).setRefresh(true); + + BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, dbq).actionGet(); + + if (response.getDeleted() > 0) { + LOGGER.info("Successfully deleted {} documents from {}", response.getDeleted(), resourceSharingIndex); + return true; + } else { + LOGGER.info( + "No documents found to delete in {} for source_idx: {} and resource_id: {}", + resourceSharingIndex, + sourceIdx, + resourceId + ); + return false; + } + + } catch (Exception e) { + LOGGER.error("Failed to delete documents from {}", resourceSharingIndex, e); + return false; + } } + /** + * Deletes all resource sharing records that were created by a specific user. + * This method performs a delete-by-query operation to remove all documents where + * the created_by.user field matches the specified username. + * + *

The method executes the following steps: + *

    + *
  1. Validates the input username parameter
  2. + *
  3. Creates a delete-by-query request with term query matching
  4. + *
  5. Executes the delete operation with immediate refresh
  6. + *
  7. Returns the operation status based on number of deleted documents
  8. + *
+ * + *

Example query structure: + *

+        * {
+        *   "query": {
+        *     "term": {
+        *       "created_by.user": "username"
+        *     }
+        *   }
+        * }
+        * 
+ * + * @param name The username to match against the created_by.user field + * @return boolean indicating whether the deletion was successful: + *
    + *
  • true - if one or more documents were deleted
  • + *
  • false - if no documents were found
  • + *
  • false - if the operation failed due to an error
  • + *
+ * + * @throws IllegalArgumentException if name is null or empty + * + * + * @implNote Implementation details: + *
    + *
  • Uses DeleteByQueryRequest for efficient bulk deletion
  • + *
  • Sets refresh=true for immediate consistency
  • + *
  • Uses term query for exact username matching
  • + *
  • Implements comprehensive error handling and logging
  • + *
+ * + * Example usage: + *
+        * boolean success = deleteAllRecordsForUser("john.doe");
+        * if (success) {
+        *     // Records were successfully deleted
+        * } else {
+        *     // No matching records found or operation failed
+        * }
+        * 
+ */ public boolean deleteAllRecordsForUser(String name) { - return false; + // Input validation + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("Username must not be null or empty"); + } + + LOGGER.info("Deleting all records for user {}", name); + + try { + DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(resourceSharingIndex).setQuery( + QueryBuilders.termQuery("created_by.user", name) + ).setRefresh(true); + + BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, deleteRequest).actionGet(); + + long deletedDocs = response.getDeleted(); + + if (deletedDocs > 0) { + LOGGER.info("Successfully deleted {} documents created by user {}", deletedDocs, name); + return true; + } else { + LOGGER.info("No documents found for user {}", name); + return false; + } + + } catch (Exception e) { + LOGGER.error("Failed to delete documents for user {}", name, e); + return false; + } } + } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index d6b1180d46..d7b149a2fb 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -14,11 +14,13 @@ import org.apache.logging.log4j.Logger; import org.opensearch.accesscontrol.resources.CreatedBy; +import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.client.Client; import org.opensearch.core.index.shard.ShardId; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexingOperationListener; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; /** @@ -36,8 +38,6 @@ public class ResourceSharingIndexListener implements IndexingOperationListener { private ThreadPool threadPool; - private Client client; - private ResourceSharingIndexListener() {} public static ResourceSharingIndexListener getInstance() { @@ -53,16 +53,12 @@ public void initialize(ThreadPool threadPool, Client client) { } initialized = true; - this.threadPool = threadPool; - - this.client = client; this.resourceSharingIndexHandler = new ResourceSharingIndexHandler( ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, client, threadPool ); - ; } @@ -73,27 +69,41 @@ public boolean isInitialized() { @Override public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult result) { - // implement a check to see if a resource was updated - log.info("postIndex called on {}", shardId.getIndexName()); + String resourceIndex = shardId.getIndexName(); + log.info("postIndex called on {}", resourceIndex); String resourceId = index.id(); - String resourceIndex = shardId.getIndexName(); + User user = threadPool.getThreadContext().getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); try { - this.resourceSharingIndexHandler.indexResourceSharing(resourceId, resourceIndex, new CreatedBy("bleh", ""), null); - log.info("successfully indexed resource {}", resourceId); + ResourceSharing sharing = this.resourceSharingIndexHandler.indexResourceSharing( + resourceId, + resourceIndex, + new CreatedBy(user.getName()), + null + ); + log.info("Successfully created a resource sharing entry {}", sharing); } catch (IOException e) { - log.info("failed to index resource {}", resourceId); - throw new RuntimeException(e); + log.info("Failed to create a resource sharing entry for resource: {}", resourceId); } } @Override public void postDelete(ShardId shardId, Engine.Delete delete, Engine.DeleteResult result) { - // implement a check to see if a resource was deleted - log.warn("postDelete called on " + shardId.getIndexName()); + String resourceIndex = shardId.getIndexName(); + log.info("postDelete called on {}", resourceIndex); + + String resourceId = delete.id(); + + boolean success = this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex); + if (success) { + log.info("Successfully deleted resource sharing entries for resource {}", resourceId); + } else { + log.info("Failed to delete resource sharing entry for resource {}", resourceId); + } + } } From d68f349d43bc7a1eb3f496d75817be8f67880aa0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Nov 2024 14:26:21 -0500 Subject: [PATCH 023/122] Fixes create API Signed-off-by: Darshit Chanpura --- .../transport/CreateResourceTransportAction.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index 53e251c5b6..f5deeb961d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -14,7 +14,6 @@ import org.apache.logging.log4j.Logger; import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.index.IndexResponse; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.action.support.WriteRequest; @@ -72,14 +71,12 @@ private void createResource(CreateResourceRequest request, ActionListener { log.info("Created resource: {}", idxResponse.toString()); }, listener::onFailure) + ); } catch (IOException e) { listener.onFailure(new RuntimeException(e)); } } - - private static ActionListener getIndexResponseActionListener(ActionListener listener) { - return ActionListener.wrap(idxResponse -> { log.info("Created resource: {}", idxResponse.toString()); }, listener::onFailure); - } - } From 58003f6881d0ede85eb624617dfb48f7e3c13daa Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Nov 2024 14:50:18 -0500 Subject: [PATCH 024/122] Fixes spotless errors Signed-off-by: Darshit Chanpura --- .../ResourceSharingIndexHandler.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 5568ee06d6..f4e2c134c1 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -10,7 +10,11 @@ package org.opensearch.security.resources; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import org.apache.commons.lang3.StringUtils; @@ -18,12 +22,20 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.search.join.ScoreMode; -import org.opensearch.accesscontrol.resources.*; +import org.opensearch.accesscontrol.resources.CreatedBy; +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; -import org.opensearch.action.search.*; +import org.opensearch.action.search.ClearScrollRequest; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.action.search.SearchScrollAction; +import org.opensearch.action.search.SearchScrollRequest; import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; import org.opensearch.common.unit.TimeValue; @@ -36,7 +48,11 @@ import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.QueryBuilders; -import org.opensearch.index.reindex.*; +import org.opensearch.index.reindex.BulkByScrollResponse; +import org.opensearch.index.reindex.DeleteByQueryAction; +import org.opensearch.index.reindex.DeleteByQueryRequest; +import org.opensearch.index.reindex.UpdateByQueryAction; +import org.opensearch.index.reindex.UpdateByQueryRequest; import org.opensearch.script.Script; import org.opensearch.script.ScriptType; import org.opensearch.search.Scroll; From 078a976edcdca9095b52d88fa6aadb9d95fd8f46 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Nov 2024 14:53:13 -0500 Subject: [PATCH 025/122] Fixes log statement Signed-off-by: Darshit Chanpura --- .../org/opensearch/security/OpenSearchSecurityPlugin.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index ccee464e01..24f146a033 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -724,7 +724,6 @@ public void onIndexModule(IndexModule indexModule) { ) ); - log.info("Indices to listen to: {}", this.indicesToListen); if (this.indicesToListen.contains(indexModule.getIndex().getName())) { ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance(); resourceSharingIndexListener.initialize(threadPool, localClient); @@ -2099,12 +2098,11 @@ public void onNodeStarted(DiscoveryNode localNode) { // create resource sharing index if absent rmr.createResourceSharingIndexIfAbsent(); - log.info("Loading resource plugins"); for (ResourcePlugin resourcePlugin : OpenSearchSecurityPlugin.GuiceHolder.getResourceService().listResourcePlugins()) { String resourceIndex = resourcePlugin.getResourceIndex(); this.indicesToListen.add(resourceIndex); - log.info("Loaded resource plugin: {}, index: {}", resourcePlugin, resourceIndex); + log.info("Preparing to listen to index: {} of plugin: {}", resourceIndex, resourcePlugin); } final Set securityModules = ReflectionHelper.getModulesLoaded(); From 04605491b15ec19c598639e21c29818e124d99ea Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 3 Dec 2024 17:37:12 -0500 Subject: [PATCH 026/122] Adds Revoke API and cleans up existing APIs Signed-off-by: Darshit Chanpura --- .../java/org/opensearch/sample/Resource.java | 6 +- .../sample/SampleResourcePlugin.java | 12 ++-- .../create/CreateResourceRestAction.java | 2 +- .../sample/actions/create/SampleResource.java | 9 ++- .../revoke/RevokeResourceAccessAction.java | 21 +++++++ .../revoke/RevokeResourceAccessRequest.java | 58 +++++++++++++++++++ .../revoke/RevokeResourceAccessResponse.java | 42 ++++++++++++++ .../RevokeResourceAccessRestAction.java | 55 ++++++++++++++++++ .../actions/share/ShareResourceRequest.java | 16 +++++ .../share/ShareResourceRestAction.java | 30 +++++++++- .../verify/VerifyResourceAccessRequest.java | 22 +++---- .../VerifyResourceAccessRestAction.java | 15 +++-- .../CreateResourceTransportAction.java | 10 ++-- ...istAccessibleResourcesTransportAction.java | 7 +-- .../RevokeResourceAccessTransportAction.java | 58 +++++++++++++++++++ .../ShareResourceTransportAction.java | 11 +--- .../VerifyResourceAccessTransportAction.java | 10 ++-- .../opensearch/sample/utils/Constants.java | 13 +++++ 18 files changed, 345 insertions(+), 52 deletions(-) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java index 36e74f1624..4ddb56f395 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java @@ -14,6 +14,8 @@ import org.opensearch.core.common.io.stream.NamedWriteable; import org.opensearch.core.xcontent.ToXContentFragment; -public abstract class Resource implements NamedWriteable, ToXContentFragment { - protected abstract String getResourceIndex(); +public interface Resource extends NamedWriteable, ToXContentFragment { + String getResourceIndex(); + + String getResourceName(); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 6ba4b82b4a..753803ddaf 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -48,18 +48,19 @@ import org.opensearch.sample.actions.create.CreateResourceRestAction; import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction; +import org.opensearch.sample.actions.revoke.RevokeResourceAccessAction; +import org.opensearch.sample.actions.revoke.RevokeResourceAccessRestAction; import org.opensearch.sample.actions.share.ShareResourceAction; import org.opensearch.sample.actions.share.ShareResourceRestAction; import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; import org.opensearch.sample.actions.verify.VerifyResourceAccessRestAction; -import org.opensearch.sample.transport.CreateResourceTransportAction; -import org.opensearch.sample.transport.ListAccessibleResourcesTransportAction; -import org.opensearch.sample.transport.ShareResourceTransportAction; -import org.opensearch.sample.transport.VerifyResourceAccessTransportAction; +import org.opensearch.sample.transport.*; import org.opensearch.script.ScriptService; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; + /** * Sample Resource plugin. * It uses ".sample_resources" index to manage its resources, and exposes a REST API @@ -68,7 +69,6 @@ public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); - public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; private Client client; @Override @@ -104,6 +104,7 @@ public List getRestHandlers( new CreateResourceRestAction(), new ListAccessibleResourcesRestAction(), new VerifyResourceAccessRestAction(), + new RevokeResourceAccessRestAction(), new ShareResourceRestAction() ); } @@ -114,6 +115,7 @@ public List getRestHandlers( new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class), new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class), new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class), + new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, RevokeResourceAccessTransportAction.class), new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class) ); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java index 86346cc279..7a9265a6b5 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java @@ -27,7 +27,7 @@ public CreateResourceRestAction() {} @Override public List routes() { - return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/resource")); + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/create")); } @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java index 1566abfe69..af3388ca14 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java @@ -18,9 +18,9 @@ import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.sample.Resource; -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; -public class SampleResource extends Resource { +public class SampleResource implements Resource { private String name; @@ -35,6 +35,11 @@ public String getResourceIndex() { return RESOURCE_INDEX_NAME; } + @Override + public String getResourceName() { + return this.name; + } + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.startObject().field("name", name).endObject(); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java new file mode 100644 index 0000000000..9261d5ad83 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.revoke; + +import org.opensearch.action.ActionType; + +public class RevokeResourceAccessAction extends ActionType { + public static final RevokeResourceAccessAction INSTANCE = new RevokeResourceAccessAction(); + + public static final String NAME = "cluster:admin/sample-resource-plugin/revoke"; + + private RevokeResourceAccessAction() { + super(NAME, RevokeResourceAccessResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java new file mode 100644 index 0000000000..504b651f8b --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.revoke; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +public class RevokeResourceAccessRequest extends ActionRequest { + + private final String resourceId; + private final Map> revokeAccess; + + public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess) { + this.resourceId = resourceId; + this.revokeAccess = revokeAccess; + } + + public RevokeResourceAccessRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.revokeAccess = in.readMap(input -> EntityType.valueOf(input.readString()), StreamInput::readStringList); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeMap( + revokeAccess, + (streamOutput, entityType) -> streamOutput.writeString(entityType.name()), + StreamOutput::writeStringCollection + ); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public Map> getRevokeAccess() { + return revokeAccess; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java new file mode 100644 index 0000000000..1236be267e --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.revoke; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class RevokeResourceAccessResponse extends ActionResponse implements ToXContentObject { + private final String message; + + public RevokeResourceAccessResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + public RevokeResourceAccessResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java new file mode 100644 index 0000000000..b5fb28ab30 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.revoke; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class RevokeResourceAccessRestAction extends BaseRestHandler { + + public RevokeResourceAccessRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/revoke")); + } + + @Override + public String getName() { + return "revoke_sample_resources_access"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + Map> revoke = (Map>) source.get("revoke"); + final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke); + return channel -> client.executeLocally( + RevokeResourceAccessAction.INSTANCE, + revokeResourceAccessRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java index 01866fd516..3c9b2cd77a 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java @@ -9,12 +9,15 @@ package org.opensearch.sample.actions.share; import java.io.IOException; +import java.util.Arrays; import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.sample.SampleResourceScope; public class ShareResourceRequest extends ActionRequest { @@ -39,6 +42,19 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { + + for (SharedWithScope s : shareWith.getSharedWithScopes()) { + try { + SampleResourceScope.valueOf(s.getScope()); + } catch (IllegalArgumentException | NullPointerException e) { + ActionRequestValidationException exception = new ActionRequestValidationException(); + exception.addValidationError( + "Invalid scope: " + s.getScope() + ". Scope must be one of: " + Arrays.toString(SampleResourceScope.values()) + ); + return exception; + } + return null; + } return null; } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java index 347fb49e68..d15901c96a 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java @@ -14,13 +14,17 @@ import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; public class ShareResourceRestAction extends BaseRestHandler { @@ -28,7 +32,7 @@ public ShareResourceRestAction() {} @Override public List routes() { - return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/share/{resource_id}")); + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/share")); } @Override @@ -44,8 +48,28 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client } String resourceId = (String) source.get("resource_id"); - ShareWith shareWith = (ShareWith) source.get("share_with"); + + ShareWith shareWith = parseShareWith(source); final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, shareWith); return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); } + + private ShareWith parseShareWith(Map source) throws IOException { + @SuppressWarnings("unchecked") + Map shareWithMap = (Map) source.get("share_with"); + if (shareWithMap == null || shareWithMap.isEmpty()) { + throw new IllegalArgumentException("share_with is required and cannot be empty"); + } + + String jsonString = XContentFactory.jsonBuilder().map(shareWithMap).toString(); + + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, jsonString) + ) { + return ShareWith.fromXContent(parser); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid share_with structure: " + e.getMessage(), e); + } + } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java index e9b20118db..f46ebf2ce6 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java @@ -9,26 +9,25 @@ package org.opensearch.sample.actions.verify; import java.io.IOException; +import java.util.Arrays; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.sample.SampleResourceScope; public class VerifyResourceAccessRequest extends ActionRequest { private final String resourceId; - private final String sourceIdx; - private final String scope; /** * Default constructor */ - public VerifyResourceAccessRequest(String resourceId, String sourceIdx, String scope) { + public VerifyResourceAccessRequest(String resourceId, String scope) { this.resourceId = resourceId; - this.sourceIdx = sourceIdx; this.scope = scope; } @@ -39,19 +38,26 @@ public VerifyResourceAccessRequest(String resourceId, String sourceIdx, String s */ public VerifyResourceAccessRequest(final StreamInput in) throws IOException { this.resourceId = in.readString(); - this.sourceIdx = in.readString(); this.scope = in.readString(); } @Override public void writeTo(final StreamOutput out) throws IOException { out.writeString(resourceId); - out.writeString(sourceIdx); out.writeString(scope); } @Override public ActionRequestValidationException validate() { + try { + SampleResourceScope.valueOf(scope); + } catch (IllegalArgumentException | NullPointerException e) { + ActionRequestValidationException exception = new ActionRequestValidationException(); + exception.addValidationError( + "Invalid scope: " + scope + ". Scope must be one of: " + Arrays.toString(SampleResourceScope.values()) + ); + return exception; + } return null; } @@ -59,10 +65,6 @@ public String getResourceId() { return resourceId; } - public String getSourceIdx() { - return sourceIdx; - } - public String getScope() { return scope; } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java index 34bfed4e9f..0d48137369 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java @@ -19,7 +19,7 @@ import org.opensearch.rest.action.RestToXContentListener; import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.rest.RestRequest.Method.GET; public class VerifyResourceAccessRestAction extends BaseRestHandler { @@ -27,7 +27,7 @@ public VerifyResourceAccessRestAction() {} @Override public List routes() { - return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/verify_resource_access")); + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/verify_resource_access")); } @Override @@ -42,11 +42,14 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client source = parser.map(); } - String resourceIdx = (String) source.get("resource_idx"); - String sourceIdx = (String) source.get("source_idx"); + String resourceId = (String) source.get("resource_id"); String scope = (String) source.get("scope"); - // final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest<>(resource); - return channel -> client.executeLocally(VerifyResourceAccessAction.INSTANCE, null, new RestToXContentListener<>(channel)); + final VerifyResourceAccessRequest verifyResourceAccessRequest = new VerifyResourceAccessRequest(resourceId, scope); + return channel -> client.executeLocally( + VerifyResourceAccessAction.INSTANCE, + verifyResourceAccessRequest, + new RestToXContentListener<>(channel) + ); } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java index f5deeb961d..4b5889153e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java @@ -31,11 +31,8 @@ import org.opensearch.transport.TransportService; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; -/** - * Transport action for CreateSampleResource. - */ public class CreateResourceTransportAction extends HandledTransportAction { private static final Logger log = LogManager.getLogger(CreateResourceTransportAction.class); @@ -54,7 +51,9 @@ protected void doExecute(Task task, CreateResourceRequest request, ActionListene ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { createResource(request, listener); - listener.onResponse(new CreateResourceResponse("Resource " + request.getResource() + " created successfully.")); + listener.onResponse( + new CreateResourceResponse("Resource " + request.getResource().getResourceName() + " created successfully.") + ); } catch (Exception e) { log.info("Failed to create resource", e); listener.onFailure(e); @@ -65,6 +64,7 @@ private void createResource(CreateResourceRequest request, ActionListener { @@ -45,7 +42,7 @@ protected void doExecute(Task task, ListAccessibleResourcesRequest request, Acti try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); - log.info("Successfully fetched accessible resources for current user"); + log.info("Successfully fetched accessible resources for current user : {}", resourceIds); listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); } catch (Exception e) { log.info("Failed to list accessible resources for current user: ", e); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java new file mode 100644 index 0000000000..fb73bccc8b --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.accesscontrol.resources.ResourceService; +import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.actions.revoke.RevokeResourceAccessAction; +import org.opensearch.sample.actions.revoke.RevokeResourceAccessRequest; +import org.opensearch.sample.actions.revoke.RevokeResourceAccessResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; + +public class RevokeResourceAccessTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(RevokeResourceAccessTransportAction.class); + + @Inject + public RevokeResourceAccessTransportAction(TransportService transportService, ActionFilters actionFilters) { + super(RevokeResourceAccessAction.NAME, transportService, actionFilters, RevokeResourceAccessRequest::new); + } + + @Override + protected void doExecute(Task task, RevokeResourceAccessRequest request, ActionListener listener) { + try { + revokeAccess(request); + listener.onResponse(new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.")); + } catch (Exception e) { + listener.onFailure(e); + } + } + + private void revokeAccess(RevokeResourceAccessRequest request) { + try { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + ResourceSharing revoke = rs.getResourceAccessControlPlugin() + .revokeAccess(request.getResourceId(), RESOURCE_INDEX_NAME, request.getRevokeAccess()); + log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); + } catch (Exception e) { + log.info("Failed to revoke access for resource {}", request.getResourceId(), e); + throw e; + } + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java index ccbfc31b78..5bd681e510 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java @@ -8,14 +8,11 @@ package org.opensearch.sample.transport; -import java.util.List; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.common.inject.Inject; @@ -27,11 +24,8 @@ import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; -import static org.opensearch.sample.SampleResourcePlugin.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; -/** - * Transport action for CreateSampleResource. - */ public class ShareResourceTransportAction extends HandledTransportAction { private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); @@ -52,10 +46,9 @@ protected void doExecute(Task task, ShareResourceRequest request, ActionListener private void shareResource(ShareResourceRequest request) { try { - ShareWith shareWith = new ShareWith(List.of()); ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); ResourceSharing sharing = rs.getResourceAccessControlPlugin() - .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, shareWith); + .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, request.getShareWith()); log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); } catch (Exception e) { log.info("Failed to share resource {}", request.getResourceId(), e); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java index 947dcec59e..9ec528d205 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java @@ -24,6 +24,8 @@ import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; + public class VerifyResourceAccessTransportAction extends HandledTransportAction { private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class); @@ -37,12 +39,12 @@ protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionL try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); boolean hasRequestedScopeAccess = rs.getResourceAccessControlPlugin() - .hasPermission(request.getResourceId(), request.getSourceIdx(), request.getScope()); + .hasPermission(request.getResourceId(), RESOURCE_INDEX_NAME, request.getScope()); StringBuilder sb = new StringBuilder(); - sb.append("User does"); - sb.append(hasRequestedScopeAccess ? " " : " not "); - sb.append("have requested scope "); + sb.append("User "); + sb.append(hasRequestedScopeAccess ? "has" : "does not have"); + sb.append(" requested scope "); sb.append(request.getScope()); sb.append(" access to "); sb.append(request.getResourceId()); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java new file mode 100644 index 0000000000..ff7404d2cd --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java @@ -0,0 +1,13 @@ +/* + * 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. + */ + +package org.opensearch.sample.utils; + +public class Constants { + public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; +} From 8e44cf333e67f07acf07141210e869262ab1dedf Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 4 Dec 2024 14:34:35 -0500 Subject: [PATCH 027/122] Renames ResourceManagement repository and add keyword to search query term Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 6 +- .../ResourceSharingIndexHandler.java | 217 +++++++++++------- ...urceSharingIndexManagementRepository.java} | 8 +- 3 files changed, 142 insertions(+), 89 deletions(-) rename src/main/java/org/opensearch/security/resources/{ResourceManagementRepository.java => ResourceSharingIndexManagementRepository.java} (72%) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 24f146a033..4297a95083 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -181,9 +181,9 @@ import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext; import org.opensearch.security.resolver.IndexResolverReplacer; import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.resources.ResourceManagementRepository; import org.opensearch.security.resources.ResourceSharingIndexHandler; import org.opensearch.security.resources.ResourceSharingIndexListener; +import org.opensearch.security.resources.ResourceSharingIndexManagementRepository; import org.opensearch.security.rest.DashboardsInfoAction; import org.opensearch.security.rest.SecurityConfigUpdateAction; import org.opensearch.security.rest.SecurityHealthAction; @@ -280,7 +280,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile OpensearchDynamicSetting transportPassiveAuthSetting; private volatile PasswordHasher passwordHasher; private volatile DlsFlsBaseContext dlsFlsBaseContext; - private ResourceManagementRepository rmr; + private ResourceSharingIndexManagementRepository rmr; private ResourceAccessHandler resourceAccessHandler; private final Set indicesToListen = new HashSet<>(); @@ -1218,7 +1218,7 @@ public Collection createComponents( ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler(resourceSharingIndex, localClient, threadPool); resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); - rmr = ResourceManagementRepository.create(rsIndexHandler); + rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler); components.add(adminDns); components.add(cr); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index f4e2c134c1..592162f206 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -216,7 +216,7 @@ public List fetchAllDocuments(String pluginIndex) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.termQuery("source_idx", pluginIndex)); + searchSourceBuilder.query(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)); searchSourceBuilder.size(10000); // TODO check what size should be set here. searchSourceBuilder.fetchSource(new String[] { "resource_id" }, null); @@ -312,7 +312,84 @@ public List fetchAllDocuments(String pluginIndex) { */ public List fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType) { - LOGGER.debug("Fetching documents from index: {}, where share_with.*.{} contains any of {}", pluginIndex, entityType, entities); + // "*" must match all scopes + return fetchDocumentsForAGivenScope(pluginIndex, entities, entityType, "*"); + } + + /** + * Fetches documents that match the specified system index and have specific access type values for a given scope. + * This method uses scroll API to handle large result sets efficiently. + * + *

The method executes the following steps: + *

    + *
  1. Validates the entityType parameter
  2. + *
  3. Creates a scrolling search request with a compound query
  4. + *
  5. Processes results in batches using scroll API
  6. + *
  7. Collects all matching resource IDs
  8. + *
  9. Cleans up scroll context
  10. + *
+ * + *

Example query structure: + *

+     * {
+     *   "query": {
+     *     "bool": {
+     *       "must": [
+     *         { "term": { "source_idx": "system_index_name" } },
+     *         {
+     *           "bool": {
+     *             "should": [
+     *               {
+     *                 "nested": {
+     *                   "path": "share_with.scope.entityType",
+     *                   "query": {
+     *                     "term": { "share_with.scope.entityType": "entity_value" }
+     *                   }
+     *                 }
+     *               }
+     *             ],
+     *             "minimum_should_match": 1
+     *           }
+     *         }
+     *       ]
+     *     }
+     *   },
+     *   "_source": ["resource_id"],
+     *   "size": 1000
+     * }
+     * 
+ * + * @param pluginIndex The source index to match against the source_idx field + * @param entities Set of values to match in the specified entityType field + * @param entityType The type of association with the resource. Must be one of: + *
    + *
  • "users" - for user-based access
  • + *
  • "roles" - for role-based access
  • + *
  • "backend_roles" - for backend role-based access
  • + *
+ * @param scope The scope of the access. Should be implementation of {@link org.opensearch.accesscontrol.resources.ResourceAccessScope} + * @return List List of resource IDs that match the criteria. The list may be empty + * if no matches are found + * + * @throws RuntimeException if the search operation fails + * + * @apiNote This method: + *
    + *
  • Uses scroll API with 1-minute timeout
  • + *
  • Processes results in batches of 1000 documents
  • + *
  • Performs source filtering for optimization
  • + *
  • Uses nested queries for accessing array elements
  • + *
  • Properly cleans up scroll context after use
  • + *
+ */ + public List fetchDocumentsForAGivenScope(String pluginIndex, Set entities, String entityType, String scope) { + LOGGER.debug( + "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", + pluginIndex, + scope, + entityType, + entities + ); List resourceIds = new ArrayList<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); @@ -321,49 +398,23 @@ public List fetchDocumentsForAllScopes(String pluginIndex, Set e SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); searchRequest.scroll(scroll); - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("source_idx", pluginIndex)); + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)); BoolQueryBuilder shouldQuery = QueryBuilders.boolQuery(); for (String entity : entities) { shouldQuery.should( QueryBuilders.nestedQuery( - "share_with.*." + entityType, - QueryBuilders.termQuery("share_with.*." + entityType, entity), + "share_with." + scope + "." + entityType, + QueryBuilders.termQuery("share_with." + scope + "." + entityType, entity), ScoreMode.None ) ); } shouldQuery.minimumShouldMatch(1); - boolQuery.must(shouldQuery); - - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) - .size(1000) - .fetchSource(new String[] { "resource_id" }, null); - - searchRequest.source(searchSourceBuilder); - - SearchResponse searchResponse = client.search(searchRequest).actionGet(); - String scrollId = searchResponse.getScrollId(); - SearchHit[] hits = searchResponse.getHits().getHits(); - - while (hits != null && hits.length > 0) { - for (SearchHit hit : hits) { - Map sourceAsMap = hit.getSourceAsMap(); - if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { - resourceIds.add(sourceAsMap.get("resource_id").toString()); - } - } - SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); - scrollRequest.scroll(scroll); - searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); - scrollId = searchResponse.getScrollId(); - hits = searchResponse.getHits().getHits(); - } + boolQuery.must(QueryBuilders.existsQuery("share_with")).must(shouldQuery); - ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); - clearScrollRequest.addScrollId(scrollId); - client.clearScroll(clearScrollRequest).actionGet(); + executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery); LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); @@ -371,9 +422,10 @@ public List fetchDocumentsForAllScopes(String pluginIndex, Set e } catch (Exception e) { LOGGER.error( - "Failed to fetch documents from {} for criteria - systemIndex: {}, shareWithType: {}, accessWays: {}", + "Failed to fetch documents from {} for criteria - systemIndex: {}, scope: {}, entityType: {}, entities: {}", resourceSharingIndex, pluginIndex, + scope, entityType, entities, e @@ -435,7 +487,6 @@ public List fetchDocumentsForAllScopes(String pluginIndex, Set e * List resources = fetchDocumentsByField("myIndex", "status", "active"); * */ - public List fetchDocumentsByField(String systemIndex, String field, String value) { if (StringUtils.isBlank(systemIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { throw new IllegalArgumentException("systemIndex, field, and value must not be null or empty"); @@ -447,48 +498,16 @@ public List fetchDocumentsByField(String systemIndex, String field, Stri final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); try { - // Create initial search request SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); searchRequest.scroll(scroll); - // Build the query BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("source_idx", systemIndex)) - .must(QueryBuilders.termQuery(field, value)); + .must(QueryBuilders.termQuery("source_idx.keyword", systemIndex)) + .must(QueryBuilders.termQuery(field + ".keyword", value)); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) - .size(1000) - .fetchSource(new String[] { "resource_id" }, null); + executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery); - searchRequest.source(searchSourceBuilder); - - // Execute initial search - SearchResponse searchResponse = client.search(searchRequest).actionGet(); - String scrollId = searchResponse.getScrollId(); - SearchHit[] hits = searchResponse.getHits().getHits(); - - // Process results in batches - while (hits != null && hits.length > 0) { - for (SearchHit hit : hits) { - Map sourceAsMap = hit.getSourceAsMap(); - if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { - resourceIds.add(sourceAsMap.get("resource_id").toString()); - } - } - - SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); - scrollRequest.scroll(scroll); - searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); - scrollId = searchResponse.getScrollId(); - hits = searchResponse.getHits().getHits(); - } - - // Clear scroll - ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); - clearScrollRequest.addScrollId(scrollId); - client.clearScroll(clearScrollRequest).actionGet(); - - LOGGER.debug("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); + LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); return resourceIds; @@ -565,8 +584,8 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("source_idx", pluginIndex)) - .must(QueryBuilders.termQuery("resource_id", resourceId)); + .must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)) + .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery).size(1); // We only need one document since // a resource must have only one @@ -603,6 +622,44 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) } } + /** + * Helper method to execute a search request and collect resource IDs from the results. + * @param resourceIds List to collect resource IDs + * @param scroll Search Scroll + * @param searchRequest Request to execute + * @param boolQuery Query to execute with the request + */ + private void executeSearchRequest(List resourceIds, Scroll scroll, SearchRequest searchRequest, BoolQueryBuilder boolQuery) { + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) + .size(1000) + .fetchSource(new String[] { "resource_id" }, null); + + searchRequest.source(searchSourceBuilder); + + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + String scrollId = searchResponse.getScrollId(); + SearchHit[] hits = searchResponse.getHits().getHits(); + + while (hits != null && hits.length > 0) { + for (SearchHit hit : hits) { + Map sourceAsMap = hit.getSourceAsMap(); + if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { + resourceIds.add(sourceAsMap.get("resource_id").toString()); + } + } + + SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); + scrollRequest.scroll(scroll); + searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); + scrollId = searchResponse.getScrollId(); + hits = searchResponse.getHits().getHits(); + } + + ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); + clearScrollRequest.addScrollId(scrollId); + client.clearScroll(clearScrollRequest).actionGet(); + } + /** * Updates resource sharing entries that match the specified source index and resource ID * using the provided update script. This method performs an update-by-query operation @@ -660,8 +717,8 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId try { // Create a bool query to match both fields BoolQueryBuilder query = QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("source_idx", sourceIdx)) - .must(QueryBuilders.termQuery("resource_id", resourceId)); + .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) + .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)); UpdateByQueryRequest ubq = new UpdateByQueryRequest(resourceSharingIndex).setQuery(query).setScript(updateScript); @@ -710,7 +767,7 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc Script updateScript = new Script( ScriptType.INLINE, "painless", - "ctx._source.shareWith = params.newShareWith", + "ctx._source.share_with = params.newShareWith", Collections.singletonMap("newShareWith", shareWith) ); @@ -737,7 +794,7 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc * "source_idx": "system_index_name", * "resource_id": "resource_id", * "share_with": { - * "group_name": { + * "scope": { * "users": ["user1", "user2"], * "roles": ["role1", "role2"], * "backend_roles": ["backend_role1"] @@ -767,11 +824,9 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc * ResourceSharing updated = revokeAccess("resourceId", "systemIndex", revokeAccess); * */ - public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { // TODO; check if this needs to be done per scope rather than for all scopes - // Input validation if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(systemIndexName) || revokeAccess == null || revokeAccess.isEmpty()) { throw new IllegalArgumentException("resourceId, systemIndexName, and revokeAccess must not be null or empty"); } @@ -779,7 +834,6 @@ public ResourceSharing revokeAccess(String resourceId, String systemIndexName, M LOGGER.debug("Revoking access for resource {} in {} for entities: {}", resourceId, systemIndexName, revokeAccess); try { - // First fetch the existing document ResourceSharing existingResource = fetchDocumentById(systemIndexName, resourceId); if (existingResource == null) { LOGGER.warn("No document found for resourceId: {} in index: {}", resourceId, systemIndexName); @@ -880,7 +934,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) try { DeleteByQueryRequest dbq = new DeleteByQueryRequest(resourceSharingIndex).setQuery( QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("source_idx", sourceIdx)) + .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) .must(QueryBuilders.termQuery("resource_id", resourceId)) ).setRefresh(true); @@ -959,7 +1013,6 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) * */ public boolean deleteAllRecordsForUser(String name) { - // Input validation if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("Username must not be null or empty"); } diff --git a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java similarity index 72% rename from src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java rename to src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java index 84749153f5..60cb48145f 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceManagementRepository.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java @@ -11,17 +11,17 @@ package org.opensearch.security.resources; -public class ResourceManagementRepository { +public class ResourceSharingIndexManagementRepository { private final ResourceSharingIndexHandler resourceSharingIndexHandler; - protected ResourceManagementRepository(final ResourceSharingIndexHandler resourceSharingIndexHandler) { + protected ResourceSharingIndexManagementRepository(final ResourceSharingIndexHandler resourceSharingIndexHandler) { this.resourceSharingIndexHandler = resourceSharingIndexHandler; } - public static ResourceManagementRepository create(ResourceSharingIndexHandler resourceSharingIndexHandler) { + public static ResourceSharingIndexManagementRepository create(ResourceSharingIndexHandler resourceSharingIndexHandler) { - return new ResourceManagementRepository(resourceSharingIndexHandler); + return new ResourceSharingIndexManagementRepository(resourceSharingIndexHandler); } /** From 16a0ba69d222a1bb7b1c344edb958dd50f6aa0e1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 4 Dec 2024 17:37:41 -0500 Subject: [PATCH 028/122] Fixes delete method Signed-off-by: Darshit Chanpura --- .../security/resources/ResourceSharingIndexHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 592162f206..7270117a1a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -935,7 +935,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) DeleteByQueryRequest dbq = new DeleteByQueryRequest(resourceSharingIndex).setQuery( QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) - .must(QueryBuilders.termQuery("resource_id", resourceId)) + .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)) ).setRefresh(true); BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, dbq).actionGet(); From 46960ea341e602c575037bc36e40131d63b181f4 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 08:58:44 -0500 Subject: [PATCH 029/122] Adds delete API and refactors package structure Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePlugin.java | 35 +++++---- .../list/ListAccessibleResourcesAction.java | 2 +- .../list/ListAccessibleResourcesRequest.java | 2 +- .../list/ListAccessibleResourcesResponse.java | 2 +- .../ListAccessibleResourcesRestAction.java | 4 +- .../revoke/RevokeResourceAccessAction.java | 2 +- .../revoke/RevokeResourceAccessRequest.java | 2 +- .../revoke/RevokeResourceAccessResponse.java | 2 +- .../RevokeResourceAccessRestAction.java | 23 ++++-- .../share/ShareResourceAction.java | 2 +- .../share/ShareResourceRequest.java | 2 +- .../share/ShareResourceResponse.java | 2 +- .../share/ShareResourceRestAction.java | 4 +- .../verify/VerifyResourceAccessAction.java | 2 +- .../verify/VerifyResourceAccessRequest.java | 2 +- .../verify/VerifyResourceAccessResponse.java | 2 +- .../VerifyResourceAccessRestAction.java | 4 +- .../create/CreateResourceAction.java | 2 +- .../create/CreateResourceRequest.java | 2 +- .../create/CreateResourceResponse.java | 2 +- .../create/CreateResourceRestAction.java | 4 +- .../{ => resource}/create/SampleResource.java | 2 +- .../resource/delete/DeleteResourceAction.java | 29 +++++++ .../delete/DeleteResourceRequest.java | 49 ++++++++++++ .../delete/DeleteResourceResponse.java | 52 +++++++++++++ .../delete/DeleteResourceRestAction.java | 49 ++++++++++++ ...istAccessibleResourcesTransportAction.java | 8 +- .../RevokeResourceAccessTransportAction.java | 8 +- .../ShareResourceTransportAction.java | 13 ++-- .../VerifyResourceAccessTransportAction.java | 8 +- .../CreateResourceTransportAction.java | 8 +- .../DeleteResourceTransportAction.java | 76 +++++++++++++++++++ 32 files changed, 343 insertions(+), 63 deletions(-) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/list/ListAccessibleResourcesAction.java (94%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/list/ListAccessibleResourcesRequest.java (95%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/list/ListAccessibleResourcesResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/list/ListAccessibleResourcesRestAction.java (89%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/revoke/RevokeResourceAccessAction.java (92%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/revoke/RevokeResourceAccessRequest.java (97%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/revoke/RevokeResourceAccessResponse.java (95%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/revoke/RevokeResourceAccessRestAction.java (59%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/share/ShareResourceAction.java (93%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/share/ShareResourceRequest.java (97%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/share/ShareResourceResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/share/ShareResourceRestAction.java (94%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/verify/VerifyResourceAccessAction.java (93%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/verify/VerifyResourceAccessRequest.java (97%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/verify/VerifyResourceAccessResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => access}/verify/VerifyResourceAccessRestAction.java (90%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => resource}/create/CreateResourceAction.java (93%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => resource}/create/CreateResourceRequest.java (95%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => resource}/create/CreateResourceResponse.java (96%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => resource}/create/CreateResourceRestAction.java (90%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/actions/{ => resource}/create/SampleResource.java (96%) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java rename sample-resource-plugin/src/main/java/org/opensearch/sample/transport/{ => access}/ListAccessibleResourcesTransportAction.java (87%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/transport/{ => access}/RevokeResourceAccessTransportAction.java (89%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/transport/{ => access}/ShareResourceTransportAction.java (81%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/transport/{ => access}/VerifyResourceAccessTransportAction.java (89%) rename sample-resource-plugin/src/main/java/org/opensearch/sample/transport/{ => resource}/CreateResourceTransportAction.java (92%) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 753803ddaf..90a62f7286 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -44,17 +44,24 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; -import org.opensearch.sample.actions.create.CreateResourceAction; -import org.opensearch.sample.actions.create.CreateResourceRestAction; -import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; -import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction; -import org.opensearch.sample.actions.revoke.RevokeResourceAccessAction; -import org.opensearch.sample.actions.revoke.RevokeResourceAccessRestAction; -import org.opensearch.sample.actions.share.ShareResourceAction; -import org.opensearch.sample.actions.share.ShareResourceRestAction; -import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; -import org.opensearch.sample.actions.verify.VerifyResourceAccessRestAction; -import org.opensearch.sample.transport.*; +import org.opensearch.sample.actions.access.list.ListAccessibleResourcesAction; +import org.opensearch.sample.actions.access.list.ListAccessibleResourcesRestAction; +import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessAction; +import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessRestAction; +import org.opensearch.sample.actions.access.share.ShareResourceAction; +import org.opensearch.sample.actions.access.share.ShareResourceRestAction; +import org.opensearch.sample.actions.access.verify.VerifyResourceAccessAction; +import org.opensearch.sample.actions.access.verify.VerifyResourceAccessRestAction; +import org.opensearch.sample.actions.resource.create.CreateResourceAction; +import org.opensearch.sample.actions.resource.create.CreateResourceRestAction; +import org.opensearch.sample.actions.resource.delete.DeleteResourceAction; +import org.opensearch.sample.actions.resource.delete.DeleteResourceRestAction; +import org.opensearch.sample.transport.access.ListAccessibleResourcesTransportAction; +import org.opensearch.sample.transport.access.RevokeResourceAccessTransportAction; +import org.opensearch.sample.transport.access.ShareResourceTransportAction; +import org.opensearch.sample.transport.access.VerifyResourceAccessTransportAction; +import org.opensearch.sample.transport.resource.CreateResourceTransportAction; +import org.opensearch.sample.transport.resource.DeleteResourceTransportAction; import org.opensearch.script.ScriptService; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; @@ -105,7 +112,8 @@ public List getRestHandlers( new ListAccessibleResourcesRestAction(), new VerifyResourceAccessRestAction(), new RevokeResourceAccessRestAction(), - new ShareResourceRestAction() + new ShareResourceRestAction(), + new DeleteResourceRestAction() ); } @@ -116,7 +124,8 @@ public List getRestHandlers( new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class), new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class), new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, RevokeResourceAccessTransportAction.class), - new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class) + new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class), + new ActionHandler<>(DeleteResourceAction.INSTANCE, DeleteResourceTransportAction.class) ); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java similarity index 94% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java index b4e9e29e22..3bea515a19 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.list; +package org.opensearch.sample.actions.access.list; import org.opensearch.action.ActionType; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java similarity index 95% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java index b4c0961774..4a9315bfd9 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.list; +package org.opensearch.sample.actions.access.list; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java index 47a8f88e4e..5c3715d143 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.list; +package org.opensearch.sample.actions.access.list; import java.io.IOException; import java.util.List; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java similarity index 89% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java index bb921fce00..2eee67e0f1 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/list/ListAccessibleResourcesRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.list; +package org.opensearch.sample.actions.access.list; import java.util.List; @@ -33,7 +33,7 @@ public String getName() { } @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(); return channel -> client.executeLocally( ListAccessibleResourcesAction.INSTANCE, diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java similarity index 92% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java index 9261d5ad83..a040cb0732 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.revoke; +package org.opensearch.sample.actions.access.revoke; import org.opensearch.action.ActionType; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java similarity index 97% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java index 504b651f8b..c59fc721f2 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.revoke; +package org.opensearch.sample.actions.access.revoke; import java.io.IOException; import java.util.List; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java similarity index 95% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java index 1236be267e..4cfd3d74e5 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.revoke; +package org.opensearch.sample.actions.access.revoke; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java similarity index 59% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java index b5fb28ab30..01e1b7591c 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/revoke/RevokeResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java @@ -6,11 +6,13 @@ * compatible open source license. */ -package org.opensearch.sample.actions.revoke; +package org.opensearch.sample.actions.access.revoke; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.client.node.NodeClient; @@ -20,7 +22,7 @@ import org.opensearch.rest.action.RestToXContentListener; import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; public class RevokeResourceAccessRestAction extends BaseRestHandler { @@ -28,7 +30,7 @@ public RevokeResourceAccessRestAction() {} @Override public List routes() { - return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/revoke")); + return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/revoke")); } @Override @@ -37,14 +39,25 @@ public String getName() { } @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); } String resourceId = (String) source.get("resource_id"); - Map> revoke = (Map>) source.get("revoke"); + @SuppressWarnings("unchecked") + Map> revokeSource = (Map>) source.get("revoke"); + Map> revoke = revokeSource.entrySet().stream().collect(Collectors.toMap(entry -> { + try { + return EntityType.fromValue(entry.getKey()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Invalid entity type: " + entry.getKey() + ". Valid values are: " + Arrays.toString(EntityType.values()) + ); + } + }, Map.Entry::getValue)); + final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke); return channel -> client.executeLocally( RevokeResourceAccessAction.INSTANCE, diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java similarity index 93% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java index d362b1927c..768a811e27 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.share; +package org.opensearch.sample.actions.access.share; import org.opensearch.action.ActionType; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java similarity index 97% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java index 3c9b2cd77a..b222364c0c 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.share; +package org.opensearch.sample.actions.access.share; import java.io.IOException; import java.util.Arrays; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java index a6a85d206d..035a9a245e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.share; +package org.opensearch.sample.actions.access.share; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java similarity index 94% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java index d15901c96a..0db4208c05 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/share/ShareResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.share; +package org.opensearch.sample.actions.access.share; import java.io.IOException; import java.util.List; @@ -41,7 +41,7 @@ public String getName() { } @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java similarity index 93% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java index 1378d561f5..466cc901c6 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.verify; +package org.opensearch.sample.actions.access.verify; import org.opensearch.action.ActionType; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java similarity index 97% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java index f46ebf2ce6..87c5b5a7f0 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.verify; +package org.opensearch.sample.actions.access.verify; import java.io.IOException; import java.util.Arrays; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java index 660ac03f71..f7c419b9d1 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.verify; +package org.opensearch.sample.actions.access.verify; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java similarity index 90% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java index 0d48137369..3118fd54e6 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/verify/VerifyResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.verify; +package org.opensearch.sample.actions.access.verify; import java.io.IOException; import java.util.List; @@ -36,7 +36,7 @@ public String getName() { } @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceAction.java similarity index 93% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceAction.java index e7c02278ab..a2b91185e1 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.create; +package org.opensearch.sample.actions.resource.create; import org.opensearch.action.ActionType; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java similarity index 95% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java index b31a4b7f2b..3f330d9719 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.create; +package org.opensearch.sample.actions.resource.create; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceResponse.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceResponse.java index 6b966ed08d..6b980c9912 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.create; +package org.opensearch.sample.actions.resource.create; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java similarity index 90% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java index 7a9265a6b5..171c539a7c 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.actions.create; +package org.opensearch.sample.actions.resource.create; import java.io.IOException; import java.util.List; @@ -36,7 +36,7 @@ public String getName() { } @Override - public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/SampleResource.java similarity index 96% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/SampleResource.java index af3388ca14..db475b7018 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/create/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/SampleResource.java @@ -9,7 +9,7 @@ * GitHub history for details. */ -package org.opensearch.sample.actions.create; +package org.opensearch.sample.actions.resource.create; import java.io.IOException; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java new file mode 100644 index 0000000000..ccb31f7ab2 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.resource.delete; + +import org.opensearch.action.ActionType; + +/** + * Action to create a sample resource + */ +public class DeleteResourceAction extends ActionType { + /** + * Create sample resource action instance + */ + public static final DeleteResourceAction INSTANCE = new DeleteResourceAction(); + /** + * Create sample resource action name + */ + public static final String NAME = "cluster:admin/sample-resource-plugin/delete"; + + private DeleteResourceAction() { + super(NAME, DeleteResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java new file mode 100644 index 0000000000..1cb58989d3 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.resource.delete; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +/** + * Request object for CreateSampleResource transport action + */ +public class DeleteResourceRequest extends ActionRequest { + + private final String resourceId; + + /** + * Default constructor + */ + public DeleteResourceRequest(String resourceId) { + this.resourceId = resourceId; + } + + public DeleteResourceRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(this.resourceId); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return this.resourceId; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java new file mode 100644 index 0000000000..ba3cddc04b --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.resource.delete; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class DeleteResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public DeleteResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public DeleteResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java new file mode 100644 index 0000000000..9a10ca2a62 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package org.opensearch.sample.actions.resource.delete; + +import java.util.List; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.common.Strings; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.DELETE; + +public class DeleteResourceRestAction extends BaseRestHandler { + + public DeleteResourceRestAction() {} + + @Override + public List routes() { + return singletonList(new Route(DELETE, "/_plugins/sample_resource_sharing/delete/{resource_id}")); + } + + @Override + public String getName() { + return "delete_sample_resource"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String resourceId = request.param("resource_id"); + if (Strings.isNullOrEmpty(resourceId)) { + throw new IllegalArgumentException("resource_id parameter is required"); + } + final DeleteResourceRequest createSampleResourceRequest = new DeleteResourceRequest(resourceId); + return channel -> client.executeLocally( + DeleteResourceAction.INSTANCE, + createSampleResourceRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java similarity index 87% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java index 7ef71e4e42..794675d3f3 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ListAccessibleResourcesTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.transport.access; import java.util.List; @@ -19,9 +19,9 @@ import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.list.ListAccessibleResourcesAction; -import org.opensearch.sample.actions.list.ListAccessibleResourcesRequest; -import org.opensearch.sample.actions.list.ListAccessibleResourcesResponse; +import org.opensearch.sample.actions.access.list.ListAccessibleResourcesAction; +import org.opensearch.sample.actions.access.list.ListAccessibleResourcesRequest; +import org.opensearch.sample.actions.access.list.ListAccessibleResourcesResponse; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java similarity index 89% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java index fb73bccc8b..14fa982e52 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/RevokeResourceAccessTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.transport.access; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,9 +18,9 @@ import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.revoke.RevokeResourceAccessAction; -import org.opensearch.sample.actions.revoke.RevokeResourceAccessRequest; -import org.opensearch.sample.actions.revoke.RevokeResourceAccessResponse; +import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessAction; +import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessRequest; +import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessResponse; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java similarity index 81% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java index 5bd681e510..e99a9abf24 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/ShareResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.transport.access; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,9 +18,9 @@ import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.share.ShareResourceAction; -import org.opensearch.sample.actions.share.ShareResourceRequest; -import org.opensearch.sample.actions.share.ShareResourceResponse; +import org.opensearch.sample.actions.access.share.ShareResourceAction; +import org.opensearch.sample.actions.access.share.ShareResourceRequest; +import org.opensearch.sample.actions.access.share.ShareResourceResponse; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -44,11 +44,14 @@ protected void doExecute(Task task, ShareResourceRequest request, ActionListener } } - private void shareResource(ShareResourceRequest request) { + private void shareResource(ShareResourceRequest request) throws Exception { try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); ResourceSharing sharing = rs.getResourceAccessControlPlugin() .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, request.getShareWith()); + if (sharing == null) { + throw new Exception("Failed to share resource " + request.getResourceId()); + } log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); } catch (Exception e) { log.info("Failed to share resource {}", request.getResourceId(), e); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java similarity index 89% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java index 9ec528d205..681e4546cc 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/VerifyResourceAccessTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.transport.access; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -18,9 +18,9 @@ import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.verify.VerifyResourceAccessAction; -import org.opensearch.sample.actions.verify.VerifyResourceAccessRequest; -import org.opensearch.sample.actions.verify.VerifyResourceAccessResponse; +import org.opensearch.sample.actions.access.verify.VerifyResourceAccessAction; +import org.opensearch.sample.actions.access.verify.VerifyResourceAccessRequest; +import org.opensearch.sample.actions.access.verify.VerifyResourceAccessResponse; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java similarity index 92% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java index 4b5889153e..9a764b61de 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.sample.transport; +package org.opensearch.sample.transport.resource; import java.io.IOException; @@ -24,9 +24,9 @@ import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.sample.Resource; -import org.opensearch.sample.actions.create.CreateResourceAction; -import org.opensearch.sample.actions.create.CreateResourceRequest; -import org.opensearch.sample.actions.create.CreateResourceResponse; +import org.opensearch.sample.actions.resource.create.CreateResourceAction; +import org.opensearch.sample.actions.resource.create.CreateResourceRequest; +import org.opensearch.sample.actions.resource.create.CreateResourceResponse; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java new file mode 100644 index 0000000000..bdc19ab8b3 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java @@ -0,0 +1,76 @@ +/* + * 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. + */ + +package org.opensearch.sample.transport.resource; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.ResourceNotFoundException; +import org.opensearch.action.DocWriteResponse; +import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.delete.DeleteResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.actions.resource.delete.DeleteResourceAction; +import org.opensearch.sample.actions.resource.delete.DeleteResourceRequest; +import org.opensearch.sample.actions.resource.delete.DeleteResourceResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; + +public class DeleteResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(DeleteResourceTransportAction.class); + + private final TransportService transportService; + private final Client nodeClient; + + @Inject + public DeleteResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(DeleteResourceAction.NAME, transportService, actionFilters, DeleteResourceRequest::new); + this.transportService = transportService; + this.nodeClient = nodeClient; + } + + @Override + protected void doExecute(Task task, DeleteResourceRequest request, ActionListener listener) { + if (request.getResourceId() == null || request.getResourceId().isEmpty()) { + listener.onFailure(new IllegalArgumentException("Resource ID cannot be null or empty")); + return; + } + + ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); + try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + deleteResource(request, ActionListener.wrap(deleteResponse -> { + if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) { + listener.onFailure(new ResourceNotFoundException("Resource " + request.getResourceId() + " not found")); + } else { + listener.onResponse(new DeleteResourceResponse("Resource " + request.getResourceId() + " deleted successfully")); + } + }, exception -> { + log.error("Failed to delete resource: " + request.getResourceId(), exception); + listener.onFailure(exception); + })); + } + } + + private void deleteResource(DeleteResourceRequest request, ActionListener listener) { + DeleteRequest deleteRequest = new DeleteRequest(RESOURCE_INDEX_NAME, request.getResourceId()).setRefreshPolicy( + WriteRequest.RefreshPolicy.IMMEDIATE + ); + + nodeClient.delete(deleteRequest, listener); + } + +} From ac53c8feb37871ceedc276786bcaab6ec8170892 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 12:32:01 -0500 Subject: [PATCH 030/122] Fixes updateByQuery painless script Signed-off-by: Darshit Chanpura --- .../ResourceSharingIndexHandler.java | 123 +++++++++++++----- 1 file changed, 90 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 7270117a1a..46e61f372a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.concurrent.Callable; +import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,10 +42,12 @@ import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.QueryBuilders; @@ -58,6 +61,7 @@ import org.opensearch.search.Scroll; import org.opensearch.search.SearchHit; import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.threadpool.ThreadPool; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; @@ -660,6 +664,88 @@ private void executeSearchRequest(List resourceIds, Scroll scroll, Searc client.clearScroll(clearScrollRequest).actionGet(); } + /** + * Updates the sharing configuration for an existing resource in the resource sharing index. + * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map)} + * This method modifies the sharing permissions for a specific resource identified by its + * resource ID and source index. + * + * @param resourceId The unique identifier of the resource whose sharing configuration needs to be updated + * @param sourceIdx The source index where the original resource is stored + * @param shareWith Updated sharing configuration object containing access control settings: + * { + * "scope": { + * "users": ["user1", "user2"], + * "roles": ["role1", "role2"], + * "backend_roles": ["backend_role1"] + * } + * } + * @return ResourceSharing Returns resourceSharing object if the update was successful, null otherwise + * @throws RuntimeException if there's an error during the update operation + */ + public ResourceSharing updateResourceSharingInfo(String resourceId, String sourceIdx, CreatedBy createdBy, ShareWith shareWith) { + XContentBuilder builder; + Map shareWithMap; + try { + builder = XContentFactory.jsonBuilder(); + shareWith.toXContent(builder, ToXContent.EMPTY_PARAMS); + String json = builder.toString(); + shareWithMap = DefaultObjectMapper.readValue(json, new TypeReference<>() { + }); + + } catch (IOException e) { + LOGGER.error("Failed to build json content", e); + return null; + } + + // Atomic operation + Script updateScript = new Script(ScriptType.INLINE, "painless", """ + if (ctx._source.share_with == null) { + ctx._source.share_with = [:]; + } + for (def entry : params.shareWith.entrySet()) { + def scopeName = entry.getKey(); + def newScope = entry.getValue(); + if (ctx._source.share_with.containsKey(scopeName)) { + def existingScope = ctx._source.share_with.get(scopeName); + if (newScope.users != null) { + if (existingScope.users == null) { + existingScope.users = new HashSet(); + } + existingScope.users.addAll(newScope.users); + } + if (newScope.roles != null) { + if (existingScope.roles == null) { + existingScope.roles = new HashSet(); + } + existingScope.roles.addAll(newScope.roles); + } + if (newScope.backend_roles != null) { + if (existingScope.backend_roles == null) { + existingScope.backend_roles = new HashSet(); + } + existingScope.backend_roles.addAll(newScope.backend_roles); + } + } else { + def newScopeEntry = [:]; + if (newScope.users != null) { + newScopeEntry.users = new HashSet(newScope.users); + } + if (newScope.roles != null) { + newScopeEntry.roles = new HashSet(newScope.roles); + } + if (newScope.backend_roles != null) { + newScopeEntry.backend_roles = new HashSet(newScope.backend_roles); + } + ctx._source.share_with.put(scopeName, newScopeEntry); + } + } + """, Collections.singletonMap("shareWith", shareWithMap)); + + boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); + return success ? new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith) : null; + } + /** * Updates resource sharing entries that match the specified source index and resource ID * using the provided update script. This method performs an update-by-query operation @@ -715,14 +801,15 @@ private void executeSearchRequest(List resourceIds, Scroll scroll, Searc */ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId, Script updateScript) { try { - // Create a bool query to match both fields BoolQueryBuilder query = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)); - UpdateByQueryRequest ubq = new UpdateByQueryRequest(resourceSharingIndex).setQuery(query).setScript(updateScript); + UpdateByQueryRequest ubq = new UpdateByQueryRequest(resourceSharingIndex).setQuery(query) + .setScript(updateScript) + .setRefresh(true); - LOGGER.info("Update By Query Request: {}", ubq.toString()); + LOGGER.debug("Update By Query Request: {}", ubq.toString()); BulkByScrollResponse response = client.execute(UpdateByQueryAction.INSTANCE, ubq).actionGet(); @@ -745,36 +832,6 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId } } - /** - * Updates the sharing configuration for an existing resource in the resource sharing index. - * This method modifies the sharing permissions for a specific resource identified by its - * resource ID and source index. - * - * @param resourceId The unique identifier of the resource whose sharing configuration needs to be updated - * @param sourceIdx The source index where the original resource is stored - * @param shareWith Updated sharing configuration object containing access control settings: - * { - * "scope": { - * "users": ["user1", "user2"], - * "roles": ["role1", "role2"], - * "backend_roles": ["backend_role1"] - * } - * } - * @return ResourceSharing Returns resourceSharing object if the update was successful, null otherwise - * @throws RuntimeException if there's an error during the update operation - */ - public ResourceSharing updateResourceSharingInfo(String resourceId, String sourceIdx, CreatedBy createdBy, ShareWith shareWith) { - Script updateScript = new Script( - ScriptType.INLINE, - "painless", - "ctx._source.share_with = params.newShareWith", - Collections.singletonMap("newShareWith", shareWith) - ); - - boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); - return success ? new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith) : null; - } - /** * Revokes access for specified entities from a resource sharing document. This method removes the specified * entities (users, roles, or backend roles) from the existing sharing configuration while preserving other From bc67926c53ab7092b3de64302649668ae3a98a1d Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 16:56:12 -0500 Subject: [PATCH 031/122] Updates Revoke request Signed-off-by: Darshit Chanpura --- .../access/revoke/RevokeResourceAccessRequest.java | 10 +++++++++- .../access/revoke/RevokeResourceAccessRestAction.java | 7 ++++--- .../access/RevokeResourceAccessTransportAction.java | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java index c59fc721f2..3b7b10f19a 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java @@ -22,15 +22,18 @@ public class RevokeResourceAccessRequest extends ActionRequest { private final String resourceId; private final Map> revokeAccess; + private final List scopes; - public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess) { + public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess, List scopes) { this.resourceId = resourceId; this.revokeAccess = revokeAccess; + this.scopes = scopes; } public RevokeResourceAccessRequest(StreamInput in) throws IOException { this.resourceId = in.readString(); this.revokeAccess = in.readMap(input -> EntityType.valueOf(input.readString()), StreamInput::readStringList); + this.scopes = in.readStringList(); } @Override @@ -41,6 +44,7 @@ public void writeTo(final StreamOutput out) throws IOException { (streamOutput, entityType) -> streamOutput.writeString(entityType.name()), StreamOutput::writeStringCollection ); + out.writeStringCollection(scopes); } @Override @@ -55,4 +59,8 @@ public String getResourceId() { public Map> getRevokeAccess() { return revokeAccess; } + + public List getScopes() { + return scopes; + } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java index 01e1b7591c..85a01d2234 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java @@ -47,7 +47,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli String resourceId = (String) source.get("resource_id"); @SuppressWarnings("unchecked") - Map> revokeSource = (Map>) source.get("revoke"); + Map> revokeSource = (Map>) source.get("entities"); Map> revoke = revokeSource.entrySet().stream().collect(Collectors.toMap(entry -> { try { return EntityType.fromValue(entry.getKey()); @@ -57,8 +57,9 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli ); } }, Map.Entry::getValue)); - - final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke); + @SuppressWarnings("unchecked") + List scopes = source.containsKey("scopes") ? (List) source.get("scopes") : List.of(); + final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke, scopes); return channel -> client.executeLocally( RevokeResourceAccessAction.INSTANCE, revokeResourceAccessRequest, diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java index 14fa982e52..dd7757e4f2 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java @@ -48,7 +48,7 @@ private void revokeAccess(RevokeResourceAccessRequest request) { try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); ResourceSharing revoke = rs.getResourceAccessControlPlugin() - .revokeAccess(request.getResourceId(), RESOURCE_INDEX_NAME, request.getRevokeAccess()); + .revokeAccess(request.getResourceId(), RESOURCE_INDEX_NAME, request.getRevokeAccess(), request.getScopes()); log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); } catch (Exception e) { log.info("Failed to revoke access for resource {}", request.getResourceId(), e); From 9e6ae850428211dd4d79608f9897a7cfeb0283cf Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 16:57:16 -0500 Subject: [PATCH 032/122] Updates revoke handler to use painless script Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 9 +- .../resources/ResourceAccessHandler.java | 9 +- .../ResourceSharingIndexHandler.java | 130 +++++++++--------- 3 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 4297a95083..8b70509390 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2223,8 +2223,13 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar } @Override - public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> entities) { - return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities); + public ResourceSharing revokeAccess( + String resourceId, + String systemIndexName, + Map> entities, + List scopes + ) { + return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities, scopes); } @Override diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index d5e79a1fdf..143dda52a3 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -116,11 +116,16 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, createdBy, shareWith); } - public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { + public ResourceSharing revokeAccess( + String resourceId, + String systemIndexName, + Map> revokeAccess, + List scopes + ) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess); - return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess); + return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes); } public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 46e61f372a..503701127a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -21,13 +21,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.lucene.search.join.ScoreMode; import org.opensearch.accesscontrol.resources.CreatedBy; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.index.IndexRequest; @@ -50,6 +48,7 @@ import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.MultiMatchQueryBuilder; import org.opensearch.index.query.QueryBuilders; import org.opensearch.index.reindex.BulkByScrollResponse; import org.opensearch.index.reindex.DeleteByQueryAction; @@ -156,8 +155,6 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn .setSource(entry.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) .request(); - LOGGER.info("Index Request: {}", ir.toString()); - ActionListener irListener = ActionListener.wrap(idxResponse -> { LOGGER.info("Successfully created {} entry.", resourceSharingIndex); }, (failResponse) -> { @@ -405,14 +402,19 @@ public List fetchDocumentsForAGivenScope(String pluginIndex, Set BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)); BoolQueryBuilder shouldQuery = QueryBuilders.boolQuery(); - for (String entity : entities) { - shouldQuery.should( - QueryBuilders.nestedQuery( - "share_with." + scope + "." + entityType, - QueryBuilders.termQuery("share_with." + scope + "." + entityType, entity), - ScoreMode.None - ) - ); + if ("*".equals(scope)) { + // Wildcard behavior: Match any scope dynamically + for (String entity : entities) { + shouldQuery.should( + QueryBuilders.multiMatchQuery(entity, "share_with.*." + entityType + ".keyword") + .type(MultiMatchQueryBuilder.Type.BEST_FIELDS) + ); + } + } else { + // Match the specific scope + for (String entity : entities) { + shouldQuery.should(QueryBuilders.termQuery("share_with." + scope + "." + entityType + ".keyword", entity)); + } } shouldQuery.minimumShouldMatch(1); @@ -666,7 +668,7 @@ private void executeSearchRequest(List resourceIds, Scroll scroll, Searc /** * Updates the sharing configuration for an existing resource in the resource sharing index. - * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map)} + * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, List)} * This method modifies the sharing permissions for a specific resource identified by its * resource ID and source index. * @@ -861,11 +863,12 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * * * @param resourceId The ID of the resource from which to revoke access - * @param systemIndexName The name of the system index where the resource exists + * @param sourceIdx The name of the system index where the resource exists * @param revokeAccess A map containing entity types (USER, ROLE, BACKEND_ROLE) and their corresponding * values to be removed from the sharing configuration + * @param scopes A list of scopes to revoke access from. If null or empty, access is revoked from all scopes * @return The updated ResourceSharing object after revoking access, or null if the document doesn't exist - * @throws IllegalArgumentException if resourceId, systemIndexName is null/empty, or if revokeAccess is null/empty + * @throws IllegalArgumentException if resourceId, sourceIdx is null/empty, or if revokeAccess is null/empty * @throws RuntimeException if the update operation fails or encounters an error * * @see EntityType @@ -881,65 +884,58 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * ResourceSharing updated = revokeAccess("resourceId", "systemIndex", revokeAccess); * */ - public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { - // TODO; check if this needs to be done per scope rather than for all scopes - - if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(systemIndexName) || revokeAccess == null || revokeAccess.isEmpty()) { - throw new IllegalArgumentException("resourceId, systemIndexName, and revokeAccess must not be null or empty"); + public ResourceSharing revokeAccess( + String resourceId, + String sourceIdx, + Map> revokeAccess, + List scopes + ) { + if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(sourceIdx) || revokeAccess == null || revokeAccess.isEmpty()) { + throw new IllegalArgumentException("resourceId, sourceIdx, and revokeAccess must not be null or empty"); } - LOGGER.debug("Revoking access for resource {} in {} for entities: {}", resourceId, systemIndexName, revokeAccess); + LOGGER.debug("Revoking access for resource {} in {} for entities: {} and scopes: {}", resourceId, sourceIdx, revokeAccess, scopes); try { - ResourceSharing existingResource = fetchDocumentById(systemIndexName, resourceId); - if (existingResource == null) { - LOGGER.warn("No document found for resourceId: {} in index: {}", resourceId, systemIndexName); - return null; - } - - ShareWith shareWith = existingResource.getShareWith(); - boolean modified = false; - - if (shareWith != null) { - for (SharedWithScope sharedWithScope : shareWith.getSharedWithScopes()) { - SharedWithScope.SharedWithPerScope sharedWithPerScope = sharedWithScope.getSharedWithPerScope(); - - for (Map.Entry> entry : revokeAccess.entrySet()) { - EntityType entityType = entry.getKey(); - List entities = entry.getValue(); - - // Check if the entity type exists in the share_with configuration - switch (entityType) { - case USERS: - if (sharedWithPerScope.getUsers() != null) { - modified = sharedWithPerScope.getUsers().removeAll(entities) || modified; - } - break; - case ROLES: - if (sharedWithPerScope.getRoles() != null) { - modified = sharedWithPerScope.getRoles().removeAll(entities) || modified; + // Revoke resource access + Script revokeScript = new Script( + ScriptType.INLINE, + "painless", + """ + if (ctx._source.share_with != null) { + List scopesToProcess = params.scopes == null || params.scopes.isEmpty() ? new ArrayList(ctx._source.share_with.keySet()) : params.scopes; + + for (def scopeName : scopesToProcess) { + if (ctx._source.share_with.containsKey(scopeName)) { + def existingScope = ctx._source.share_with.get(scopeName); + + for (def entry : params.revokeAccess.entrySet()) { + def entityType = entry.getKey(); + def entitiesToRemove = entry.getValue(); + + if (existingScope.containsKey(entityType) && existingScope[entityType] != null) { + existingScope[entityType].removeAll(entitiesToRemove); + if (existingScope[entityType].isEmpty()) { + existingScope.remove(entityType); + } + } + } + + if (existingScope.isEmpty()) { + ctx._source.share_with.remove(scopeName); + } } - break; - case BACKEND_ROLES: - if (sharedWithPerScope.getBackendRoles() != null) { - modified = sharedWithPerScope.getBackendRoles().removeAll(entities) || modified; - } - break; + } } - } - } - } - - if (!modified) { - LOGGER.debug("No modifications needed for resource: {}", resourceId); - return existingResource; - } + """, + Map.of("revokeAccess", revokeAccess, "scopes", scopes) + ); - // Update resource sharing info - return updateResourceSharingInfo(resourceId, systemIndexName, existingResource.getCreatedBy(), shareWith); + boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, revokeScript); + return success ? fetchDocumentById(sourceIdx, resourceId) : null; } catch (Exception e) { - LOGGER.error("Failed to revoke access for resource: {} in index: {}", resourceId, systemIndexName, e); + LOGGER.error("Failed to revoke access for resource: {} in index: {}", resourceId, sourceIdx, e); throw new RuntimeException("Failed to revoke access: " + e.getMessage(), e); } } @@ -986,7 +982,7 @@ public ResourceSharing revokeAccess(String resourceId, String systemIndexName, M * */ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) { - LOGGER.info("Deleting documents from {} where source_idx = {} and resource_id = {}", resourceSharingIndex, sourceIdx, resourceId); + LOGGER.debug("Deleting documents from {} where source_idx = {} and resource_id = {}", resourceSharingIndex, sourceIdx, resourceId); try { DeleteByQueryRequest dbq = new DeleteByQueryRequest(resourceSharingIndex).setQuery( @@ -1074,7 +1070,7 @@ public boolean deleteAllRecordsForUser(String name) { throw new IllegalArgumentException("Username must not be null or empty"); } - LOGGER.info("Deleting all records for user {}", name); + LOGGER.debug("Deleting all records for user {}", name); try { DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(resourceSharingIndex).setQuery( From 0fe9779de06e7b57ecb9358d3c517d9cf64f9add Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 17:58:37 -0500 Subject: [PATCH 033/122] Convert sets to lists Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 6 +-- .../resources/ResourceAccessHandler.java | 17 ++++--- .../ResourceSharingIndexHandler.java | 48 +++++++++---------- 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 8b70509390..0e3e612e20 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2208,7 +2208,7 @@ private void tryAddSecurityProvider() { } @Override - public List listAccessibleResourcesInPlugin(String systemIndexName) { + public Set listAccessibleResourcesInPlugin(String systemIndexName) { return this.resourceAccessHandler.listAccessibleResourcesInPlugin(systemIndexName); } @@ -2226,8 +2226,8 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar public ResourceSharing revokeAccess( String resourceId, String systemIndexName, - Map> entities, - List scopes + Map> entities, + Set scopes ) { return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities, scopes); } diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 143dda52a3..7812778981 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -13,7 +13,6 @@ import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -49,11 +48,11 @@ public ResourceAccessHandler( this.adminDNs = adminDns; } - public List listAccessibleResourcesInPlugin(String pluginIndex) { + public Set listAccessibleResourcesInPlugin(String pluginIndex) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); - return Collections.emptyList(); + return Collections.emptySet(); } LOGGER.info("Listing accessible resource within a system index {} for : {}", pluginIndex, user.getName()); @@ -79,7 +78,7 @@ public List listAccessibleResourcesInPlugin(String pluginIndex) { Set backendRoles = user.getRoles(); result.addAll(loadSharedWithResources(pluginIndex, backendRoles, EntityType.BACKEND_ROLES.toString())); - return result.stream().toList(); + return result; } public boolean hasPermission(String resourceId, String systemIndexName, String scope) { @@ -119,8 +118,8 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar public ResourceSharing revokeAccess( String resourceId, String systemIndexName, - Map> revokeAccess, - List scopes + Map> revokeAccess, + Set scopes ) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess); @@ -153,16 +152,16 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() { // Helper methods - private List loadAllResources(String systemIndex) { + private Set loadAllResources(String systemIndex) { return this.resourceSharingIndexHandler.fetchAllDocuments(systemIndex); } - private List loadOwnResources(String systemIndex, String username) { + private Set loadOwnResources(String systemIndex, String username) { // TODO check if this magic variable can be replaced return this.resourceSharingIndexHandler.fetchDocumentsByField(systemIndex, "created_by.user", username); } - private List loadSharedWithResources(String systemIndex, Set entities, String shareWithType) { + private Set loadSharedWithResources(String systemIndex, Set entities, String shareWithType) { return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(systemIndex, entities, shareWithType); } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 503701127a..309daa93d6 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -10,11 +10,7 @@ package org.opensearch.security.resources; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; import com.fasterxml.jackson.core.type.TypeReference; @@ -195,7 +191,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn * * * @param pluginIndex The source index to match against the source_idx field - * @return List containing resource IDs that belong to the specified system index. + * @return Set containing resource IDs that belong to the specified system index. * Returns an empty list if: *
    *
  • No matching documents are found
  • @@ -210,7 +206,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn *
  • Returns an empty list instead of throwing exceptions
  • *
*/ - public List fetchAllDocuments(String pluginIndex) { + public Set fetchAllDocuments(String pluginIndex) { LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); try { @@ -226,7 +222,7 @@ public List fetchAllDocuments(String pluginIndex) { SearchResponse searchResponse = client.search(searchRequest).actionGet(); - List resourceIds = new ArrayList<>(); + Set resourceIds = new HashSet<>(); SearchHit[] hits = searchResponse.getHits().getHits(); for (SearchHit hit : hits) { @@ -242,7 +238,7 @@ public List fetchAllDocuments(String pluginIndex) { } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); - return List.of(); + return Set.of(); } } @@ -297,7 +293,7 @@ public List fetchAllDocuments(String pluginIndex) { *
  • "roles" - for role-based access
  • *
  • "backend_roles" - for backend role-based access
  • * - * @return List List of resource IDs that match the criteria. The list may be empty + * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found * * @throws RuntimeException if the search operation fails @@ -312,7 +308,7 @@ public List fetchAllDocuments(String pluginIndex) { * */ - public List fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType) { + public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType) { // "*" must match all scopes return fetchDocumentsForAGivenScope(pluginIndex, entities, entityType, "*"); } @@ -369,7 +365,7 @@ public List fetchDocumentsForAllScopes(String pluginIndex, Set e *
  • "backend_roles" - for backend role-based access
  • * * @param scope The scope of the access. Should be implementation of {@link org.opensearch.accesscontrol.resources.ResourceAccessScope} - * @return List List of resource IDs that match the criteria. The list may be empty + * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found * * @throws RuntimeException if the search operation fails @@ -383,7 +379,7 @@ public List fetchDocumentsForAllScopes(String pluginIndex, Set e *
  • Properly cleans up scroll context after use
  • * */ - public List fetchDocumentsForAGivenScope(String pluginIndex, Set entities, String entityType, String scope) { + public Set fetchDocumentsForAGivenScope(String pluginIndex, Set entities, String entityType, String scope) { LOGGER.debug( "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", pluginIndex, @@ -392,7 +388,7 @@ public List fetchDocumentsForAGivenScope(String pluginIndex, Set entities ); - List resourceIds = new ArrayList<>(); + Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); try { @@ -473,7 +469,7 @@ public List fetchDocumentsForAGivenScope(String pluginIndex, Set * @param systemIndex The source index to match against the source_idx field * @param field The field name to search in. Must be a valid field in the index mapping * @param value The value to match for the specified field. Performs exact term matching - * @return List List of resource IDs that match the criteria. Returns an empty list + * @return Set List of resource IDs that match the criteria. Returns an empty list * if no matches are found * * @throws IllegalArgumentException if any parameter is null or empty @@ -490,17 +486,17 @@ public List fetchDocumentsForAGivenScope(String pluginIndex, Set * * Example usage: *
    -     * List resources = fetchDocumentsByField("myIndex", "status", "active");
    +     * Set resources = fetchDocumentsByField("myIndex", "status", "active");
          * 
    */ - public List fetchDocumentsByField(String systemIndex, String field, String value) { + public Set fetchDocumentsByField(String systemIndex, String field, String value) { if (StringUtils.isBlank(systemIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { throw new IllegalArgumentException("systemIndex, field, and value must not be null or empty"); } LOGGER.debug("Fetching documents from index: {}, where {} = {}", systemIndex, field, value); - List resourceIds = new ArrayList<>(); + Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); try { @@ -635,7 +631,7 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) * @param searchRequest Request to execute * @param boolQuery Query to execute with the request */ - private void executeSearchRequest(List resourceIds, Scroll scroll, SearchRequest searchRequest, BoolQueryBuilder boolQuery) { + private void executeSearchRequest(Set resourceIds, Scroll scroll, SearchRequest searchRequest, BoolQueryBuilder boolQuery) { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) .size(1000) .fetchSource(new String[] { "resource_id" }, null); @@ -668,7 +664,7 @@ private void executeSearchRequest(List resourceIds, Scroll scroll, Searc /** * Updates the sharing configuration for an existing resource in the resource sharing index. - * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, List)} + * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set)} * This method modifies the sharing permissions for a specific resource identified by its * resource ID and source index. * @@ -878,17 +874,17 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * entities don't exist in the current configuration), the original document is returned unchanged. * @example *
    -     * Map> revokeAccess = new HashMap<>();
    -     * revokeAccess.put(EntityType.USER, Arrays.asList("user1", "user2"));
    -     * revokeAccess.put(EntityType.ROLE, Arrays.asList("role1"));
    +     * Map> revokeAccess = new HashMap<>();
    +     * revokeAccess.put(EntityType.USER, Set.of("user1", "user2"));
    +     * revokeAccess.put(EntityType.ROLE, Set.of("role1"));
          * ResourceSharing updated = revokeAccess("resourceId", "systemIndex", revokeAccess);
          * 
    */ public ResourceSharing revokeAccess( String resourceId, String sourceIdx, - Map> revokeAccess, - List scopes + Map> revokeAccess, + Set scopes ) { if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(sourceIdx) || revokeAccess == null || revokeAccess.isEmpty()) { throw new IllegalArgumentException("resourceId, sourceIdx, and revokeAccess must not be null or empty"); @@ -903,7 +899,7 @@ public ResourceSharing revokeAccess( "painless", """ if (ctx._source.share_with != null) { - List scopesToProcess = params.scopes == null || params.scopes.isEmpty() ? new ArrayList(ctx._source.share_with.keySet()) : params.scopes; + Set scopesToProcess = params.scopes == null || params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes; for (def scopeName : scopesToProcess) { if (ctx._source.share_with.containsKey(scopeName)) { From ccd5d07fb98c8ec586f87a513085cf67385a8d9e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 18:15:35 -0500 Subject: [PATCH 034/122] Convert sets to lists Signed-off-by: Darshit Chanpura --- .../list/ListAccessibleResourcesResponse.java | 8 ++--- .../revoke/RevokeResourceAccessRequest.java | 22 ++++++++----- .../RevokeResourceAccessRestAction.java | 7 ++-- .../access/share/ShareResourceRequest.java | 20 +++--------- .../verify/VerifyResourceAccessRequest.java | 15 ++------- ...istAccessibleResourcesTransportAction.java | 4 +-- .../opensearch/sample/utils/Validation.java | 32 +++++++++++++++++++ 7 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java index 5c3715d143..fb1112bc1d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java @@ -9,7 +9,7 @@ package org.opensearch.sample.actions.access.list; import java.io.IOException; -import java.util.List; +import java.util.Set; import org.opensearch.core.action.ActionResponse; import org.opensearch.core.common.io.stream.StreamInput; @@ -21,9 +21,9 @@ * Response to a ListAccessibleResourcesRequest */ public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { - private final List resourceIds; + private final Set resourceIds; - public ListAccessibleResourcesResponse(List resourceIds) { + public ListAccessibleResourcesResponse(Set resourceIds) { this.resourceIds = resourceIds; } @@ -33,7 +33,7 @@ public void writeTo(StreamOutput out) throws IOException { } public ListAccessibleResourcesResponse(final StreamInput in) throws IOException { - resourceIds = in.readStringList(); + resourceIds = in.readSet(StreamInput::readString); } @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java index 3b7b10f19a..e97a2d1244 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java @@ -9,22 +9,23 @@ package org.opensearch.sample.actions.access.revoke; import java.io.IOException; -import java.util.List; import java.util.Map; +import java.util.Set; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.sample.utils.Validation; public class RevokeResourceAccessRequest extends ActionRequest { private final String resourceId; - private final Map> revokeAccess; - private final List scopes; + private final Map> revokeAccess; + private final Set scopes; - public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess, List scopes) { + public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess, Set scopes) { this.resourceId = resourceId; this.revokeAccess = revokeAccess; this.scopes = scopes; @@ -32,8 +33,8 @@ public RevokeResourceAccessRequest(String resourceId, Map EntityType.valueOf(input.readString()), StreamInput::readStringList); - this.scopes = in.readStringList(); + this.revokeAccess = in.readMap(input -> EntityType.valueOf(input.readString()), input -> input.readSet(StreamInput::readString)); + this.scopes = in.readSet(StreamInput::readString); } @Override @@ -49,6 +50,11 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { + + if (!(this.scopes == null)) { + return Validation.validateScopes(this.scopes); + } + return null; } @@ -56,11 +62,11 @@ public String getResourceId() { return resourceId; } - public Map> getRevokeAccess() { + public Map> getRevokeAccess() { return revokeAccess; } - public List getScopes() { + public Set getScopes() { return scopes; } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java index 85a01d2234..3fe0a2329e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.opensearch.accesscontrol.resources.EntityType; @@ -47,8 +48,8 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli String resourceId = (String) source.get("resource_id"); @SuppressWarnings("unchecked") - Map> revokeSource = (Map>) source.get("entities"); - Map> revoke = revokeSource.entrySet().stream().collect(Collectors.toMap(entry -> { + Map> revokeSource = (Map>) source.get("entities"); + Map> revoke = revokeSource.entrySet().stream().collect(Collectors.toMap(entry -> { try { return EntityType.fromValue(entry.getKey()); } catch (IllegalArgumentException e) { @@ -58,7 +59,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli } }, Map.Entry::getValue)); @SuppressWarnings("unchecked") - List scopes = source.containsKey("scopes") ? (List) source.get("scopes") : List.of(); + Set scopes = source.containsKey("scopes") ? (Set) source.get("scopes") : Set.of(); final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke, scopes); return channel -> client.executeLocally( RevokeResourceAccessAction.INSTANCE, diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java index b222364c0c..6c2ed12e73 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java @@ -9,7 +9,7 @@ package org.opensearch.sample.actions.access.share; import java.io.IOException; -import java.util.Arrays; +import java.util.stream.Collectors; import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.accesscontrol.resources.SharedWithScope; @@ -17,7 +17,7 @@ import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.SampleResourceScope; +import org.opensearch.sample.utils.Validation; public class ShareResourceRequest extends ActionRequest { @@ -43,19 +43,9 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { - for (SharedWithScope s : shareWith.getSharedWithScopes()) { - try { - SampleResourceScope.valueOf(s.getScope()); - } catch (IllegalArgumentException | NullPointerException e) { - ActionRequestValidationException exception = new ActionRequestValidationException(); - exception.addValidationError( - "Invalid scope: " + s.getScope() + ". Scope must be one of: " + Arrays.toString(SampleResourceScope.values()) - ); - return exception; - } - return null; - } - return null; + return Validation.validateScopes( + shareWith.getSharedWithScopes().stream().map(SharedWithScope::getScope).collect(Collectors.toSet()) + ); } public String getResourceId() { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java index 87c5b5a7f0..b9ab4134c6 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java @@ -9,13 +9,13 @@ package org.opensearch.sample.actions.access.verify; import java.io.IOException; -import java.util.Arrays; +import java.util.Set; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.SampleResourceScope; +import org.opensearch.sample.utils.Validation; public class VerifyResourceAccessRequest extends ActionRequest { @@ -49,16 +49,7 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public ActionRequestValidationException validate() { - try { - SampleResourceScope.valueOf(scope); - } catch (IllegalArgumentException | NullPointerException e) { - ActionRequestValidationException exception = new ActionRequestValidationException(); - exception.addValidationError( - "Invalid scope: " + scope + ". Scope must be one of: " + Arrays.toString(SampleResourceScope.values()) - ); - return exception; - } - return null; + return Validation.validateScopes(Set.of(scope)); } public String getResourceId() { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java index 794675d3f3..2ca748c7d5 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java @@ -8,7 +8,7 @@ package org.opensearch.sample.transport.access; -import java.util.List; +import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,7 +41,7 @@ public ListAccessibleResourcesTransportAction(TransportService transportService, protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - List resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); + Set resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); log.info("Successfully fetched accessible resources for current user : {}", resourceIds); listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); } catch (Exception e) { diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java new file mode 100644 index 0000000000..13d7761584 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package org.opensearch.sample.utils; + +import java.util.Arrays; +import java.util.Set; + +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.sample.SampleResourceScope; + +public class Validation { + public static ActionRequestValidationException validateScopes(Set scopes) { + for (String s : scopes) { + try { + SampleResourceScope.valueOf(s); + } catch (IllegalArgumentException | NullPointerException e) { + ActionRequestValidationException exception = new ActionRequestValidationException(); + exception.addValidationError( + "Invalid scope: " + s + ". Scope must be one of: " + Arrays.toString(SampleResourceScope.values()) + ); + return exception; + } + } + return null; + } +} From bfc39ad849d3a6b583d4d5bb4ca83838ff838224 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 19:05:07 -0500 Subject: [PATCH 035/122] Adds default scopes to validation list Signed-off-by: Darshit Chanpura --- .../opensearch/sample/utils/Validation.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java index 13d7761584..a057d41eed 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java @@ -8,22 +8,26 @@ package org.opensearch.sample.utils; -import java.util.Arrays; +import java.util.HashSet; import java.util.Set; +import org.opensearch.accesscontrol.resources.ResourceAccessScope; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.sample.SampleResourceScope; public class Validation { public static ActionRequestValidationException validateScopes(Set scopes) { + Set validScopes = new HashSet<>(); + for (SampleResourceScope scope : SampleResourceScope.values()) { + validScopes.add(scope.name()); + } + validScopes.add(ResourceAccessScope.READ_ONLY); + validScopes.add(ResourceAccessScope.READ_WRITE); + for (String s : scopes) { - try { - SampleResourceScope.valueOf(s); - } catch (IllegalArgumentException | NullPointerException e) { + if (!validScopes.contains(s)) { ActionRequestValidationException exception = new ActionRequestValidationException(); - exception.addValidationError( - "Invalid scope: " + s + ". Scope must be one of: " + Arrays.toString(SampleResourceScope.values()) - ); + exception.addValidationError("Invalid scope: " + s + ". Scope must be one of: " + validScopes); return exception; } } From acc22c4ea9ca018916b1b619444b8aabfb0e0abe Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 19:16:27 -0500 Subject: [PATCH 036/122] Fixes ClassCastException Signed-off-by: Darshit Chanpura --- .../access/revoke/RevokeResourceAccessRestAction.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java index 3fe0a2329e..1145457863 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java @@ -9,10 +9,7 @@ package org.opensearch.sample.actions.access.revoke; import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import org.opensearch.accesscontrol.resources.EntityType; @@ -59,7 +56,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli } }, Map.Entry::getValue)); @SuppressWarnings("unchecked") - Set scopes = source.containsKey("scopes") ? (Set) source.get("scopes") : Set.of(); + Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke, scopes); return channel -> client.executeLocally( RevokeResourceAccessAction.INSTANCE, From 6d7f4c0e25cefef3986e52d712aea3ba7a9c60eb Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 19:48:35 -0500 Subject: [PATCH 037/122] Explicitly casts painless entries to set to avoid duplicates Signed-off-by: Darshit Chanpura --- .../resources/ResourceSharingIndexHandler.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 309daa93d6..3c1dd9771f 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -10,7 +10,10 @@ package org.opensearch.security.resources; import java.io.IOException; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import com.fasterxml.jackson.core.type.TypeReference; @@ -697,7 +700,9 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc } // Atomic operation - Script updateScript = new Script(ScriptType.INLINE, "painless", """ + // TODO check if this script can be updated to replace magic identifiers (i.e. users, roles and backend_roles) with the ones + // supplied in shareWith + Script updateScript = new Script(ScriptType.INLINE, """ if (ctx._source.share_with == null) { ctx._source.share_with = [:]; } @@ -710,18 +715,20 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc if (existingScope.users == null) { existingScope.users = new HashSet(); } + existingScope.users = new HashSet<>(existingScope.users); existingScope.users.addAll(newScope.users); } - if (newScope.roles != null) { if (existingScope.roles == null) { existingScope.roles = new HashSet(); } + existingScope.roles = new HashSet<>(existingScope.roles); existingScope.roles.addAll(newScope.roles); } if (newScope.backend_roles != null) { if (existingScope.backend_roles == null) { existingScope.backend_roles = new HashSet(); } + existingScope.backend_roles = new HashSet<>(existingScope.backend_roles); existingScope.backend_roles.addAll(newScope.backend_roles); } } else { @@ -738,7 +745,7 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc ctx._source.share_with.put(scopeName, newScopeEntry); } } - """, Collections.singletonMap("shareWith", shareWithMap)); + """, "painless", Collections.singletonMap("shareWith", shareWithMap)); boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); return success ? new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith) : null; @@ -899,7 +906,7 @@ public ResourceSharing revokeAccess( "painless", """ if (ctx._source.share_with != null) { - Set scopesToProcess = params.scopes == null || params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes; + Set scopesToProcess = new HashSet(params.scopes == null || params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes); for (def scopeName : scopesToProcess) { if (ctx._source.share_with.containsKey(scopeName)) { From 9d4ca1e2c607126bd27f9add0ec90e1b3e269df0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 19:51:30 -0500 Subject: [PATCH 038/122] Fixes revoke access script Signed-off-by: Darshit Chanpura --- .../security/resources/ResourceSharingIndexHandler.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 3c1dd9771f..b460fdc964 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -918,15 +918,8 @@ public ResourceSharing revokeAccess( if (existingScope.containsKey(entityType) && existingScope[entityType] != null) { existingScope[entityType].removeAll(entitiesToRemove); - if (existingScope[entityType].isEmpty()) { - existingScope.remove(entityType); - } } } - - if (existingScope.isEmpty()) { - ctx._source.share_with.remove(scopeName); - } } } } From b4b22d6ff6357c2d345d40c99a2eb98452d87d17 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 6 Dec 2024 15:11:30 -0500 Subject: [PATCH 039/122] Fixes revoke script to handle duplicates Signed-off-by: Darshit Chanpura --- .../ResourceSharingIndexHandler.java | 121 ++++++++++-------- 1 file changed, 67 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index b460fdc964..cce026b8be 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -10,8 +10,11 @@ package org.opensearch.security.resources; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -702,50 +705,45 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc // Atomic operation // TODO check if this script can be updated to replace magic identifiers (i.e. users, roles and backend_roles) with the ones // supplied in shareWith - Script updateScript = new Script(ScriptType.INLINE, """ + Script updateScript = new Script(ScriptType.INLINE, "painless", """ if (ctx._source.share_with == null) { ctx._source.share_with = [:]; } + for (def entry : params.shareWith.entrySet()) { def scopeName = entry.getKey(); def newScope = entry.getValue(); - if (ctx._source.share_with.containsKey(scopeName)) { - def existingScope = ctx._source.share_with.get(scopeName); - if (newScope.users != null) { - if (existingScope.users == null) { - existingScope.users = new HashSet(); - } - existingScope.users = new HashSet<>(existingScope.users); - existingScope.users.addAll(newScope.users); - } - if (existingScope.roles == null) { - existingScope.roles = new HashSet(); - } - existingScope.roles = new HashSet<>(existingScope.roles); - existingScope.roles.addAll(newScope.roles); - } - if (newScope.backend_roles != null) { - if (existingScope.backend_roles == null) { - existingScope.backend_roles = new HashSet(); + + if (!ctx._source.share_with.containsKey(scopeName)) { + def newScopeEntry = [:]; + for (def field : newScope.entrySet()) { + if (field.getValue() != null && !field.getValue().isEmpty()) { + newScopeEntry[field.getKey()] = new HashSet(field.getValue()); } - existingScope.backend_roles = new HashSet<>(existingScope.backend_roles); - existingScope.backend_roles.addAll(newScope.backend_roles); } + ctx._source.share_with[scopeName] = newScopeEntry; } else { - def newScopeEntry = [:]; - if (newScope.users != null) { - newScopeEntry.users = new HashSet(newScope.users); - } - if (newScope.roles != null) { - newScopeEntry.roles = new HashSet(newScope.roles); - } - if (newScope.backend_roles != null) { - newScopeEntry.backend_roles = new HashSet(newScope.backend_roles); + def existingScope = ctx._source.share_with[scopeName]; + + for (def field : newScope.entrySet()) { + def fieldName = field.getKey(); + def newValues = field.getValue(); + + if (newValues != null && !newValues.isEmpty()) { + if (!existingScope.containsKey(fieldName)) { + existingScope[fieldName] = new HashSet(); + } + + for (def value : newValues) { + if (!existingScope[fieldName].contains(value)) { + existingScope[fieldName].add(value); + } + } + } } - ctx._source.share_with.put(scopeName, newScopeEntry); } } - """, "painless", Collections.singletonMap("shareWith", shareWithMap)); + """, Collections.singletonMap("shareWith", shareWithMap)); boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); return success ? new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith) : null; @@ -900,34 +898,49 @@ public ResourceSharing revokeAccess( LOGGER.debug("Revoking access for resource {} in {} for entities: {} and scopes: {}", resourceId, sourceIdx, revokeAccess, scopes); try { - // Revoke resource access - Script revokeScript = new Script( - ScriptType.INLINE, - "painless", - """ - if (ctx._source.share_with != null) { - Set scopesToProcess = new HashSet(params.scopes == null || params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes); - - for (def scopeName : scopesToProcess) { - if (ctx._source.share_with.containsKey(scopeName)) { - def existingScope = ctx._source.share_with.get(scopeName); - - for (def entry : params.revokeAccess.entrySet()) { - def entityType = entry.getKey(); - def entitiesToRemove = entry.getValue(); - - if (existingScope.containsKey(entityType) && existingScope[entityType] != null) { - existingScope[entityType].removeAll(entitiesToRemove); - } + Map revoke = new HashMap<>(); + for (Map.Entry> entry : revokeAccess.entrySet()) { + revoke.put(entry.getKey().name().toLowerCase(), new ArrayList<>(entry.getValue())); + } + + List scopesToUse = scopes != null ? new ArrayList<>(scopes) : new ArrayList<>(); + + Script revokeScript = new Script(ScriptType.INLINE, "painless", """ + if (ctx._source.share_with != null) { + Set scopesToProcess = new HashSet(params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes); + + for (def scopeName : scopesToProcess) { + if (ctx._source.share_with.containsKey(scopeName)) { + def existingScope = ctx._source.share_with.get(scopeName); + + for (def entry : params.revokeAccess.entrySet()) { + def entityType = entry.getKey(); + def entitiesToRemove = entry.getValue(); + + if (existingScope.containsKey(entityType) && existingScope[entityType] != null) { + if (!(existingScope[entityType] instanceof HashSet)) { + existingScope[entityType] = new HashSet(existingScope[entityType]); + } + + existingScope[entityType].removeAll(entitiesToRemove); + + if (existingScope[entityType].isEmpty()) { + existingScope.remove(entityType); } } } + + if (existingScope.isEmpty()) { + ctx._source.share_with.remove(scopeName); + } } - """, - Map.of("revokeAccess", revokeAccess, "scopes", scopes) - ); + } + } + """, Map.of("revokeAccess", revoke, "scopes", scopesToUse)); + // Execute updateByQuery boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, revokeScript); + return success ? fetchDocumentById(sourceIdx, resourceId) : null; } catch (Exception e) { From e87bb80d9e899e0eae8dbb98c6e18a15355226ed Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 7 Dec 2024 00:47:15 -0500 Subject: [PATCH 040/122] Updates logger statement Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessHandler.java | 27 ++++++++----------- .../ResourceSharingIndexHandler.java | 3 --- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 7812778981..74d83db8c1 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -96,9 +96,9 @@ public boolean hasPermission(String resourceId, String systemIndexName, String s if (isSharedWithEveryone(document) || isOwnerOfResource(document, user.getName()) - || isSharedWithUser(document, user.getName(), scope) - || isSharedWithGroup(document, userRoles, scope) - || isSharedWithGroup(document, userBackendRoles, scope)) { + || isSharedWithEntity(document, EntityType.USERS, Set.of(user.getName()), scope) + || isSharedWithEntity(document, EntityType.ROLES, userRoles, scope) + || isSharedWithEntity(document, EntityType.BACKEND_ROLES, userBackendRoles, scope)) { LOGGER.info("User {} has {} access to {}", user.getName(), scope, resourceId); return true; } @@ -122,7 +122,7 @@ public ResourceSharing revokeAccess( Set scopes ) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Revoking access to resource {} created by {} for {}", resourceId, user.getName(), revokeAccess); + LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes); return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes); } @@ -169,13 +169,9 @@ private boolean isOwnerOfResource(ResourceSharing document, String userName) { return document.getCreatedBy() != null && document.getCreatedBy().getUser().equals(userName); } - private boolean isSharedWithUser(ResourceSharing document, String userName, String scope) { - return checkSharing(document, "users", userName, scope); - } - - private boolean isSharedWithGroup(ResourceSharing document, Set roles, String scope) { + private boolean isSharedWithEntity(ResourceSharing document, EntityType entityType, Set roles, String scope) { for (String role : roles) { - if (checkSharing(document, "roles", role, scope)) { + if (checkSharing(document, entityType, role, scope)) { return true; } } @@ -187,7 +183,7 @@ private boolean isSharedWithEveryone(ResourceSharing document) { && document.getShareWith().getSharedWithScopes().stream().anyMatch(sharedWithScope -> sharedWithScope.getScope().equals("*")); } - private boolean checkSharing(ResourceSharing document, String sharingType, String identifier, String scope) { + private boolean checkSharing(ResourceSharing document, EntityType entityType, String identifier, String scope) { if (document.getShareWith() == null) { return false; } @@ -200,11 +196,10 @@ private boolean checkSharing(ResourceSharing document, String sharingType, Strin .map(sharedWithScope -> { SharedWithScope.SharedWithPerScope scopePermissions = sharedWithScope.getSharedWithPerScope(); - return switch (sharingType) { - case "users" -> scopePermissions.getUsers().contains(identifier); - case "roles" -> scopePermissions.getRoles().contains(identifier); - case "backend_roles" -> scopePermissions.getBackendRoles().contains(identifier); - default -> false; + return switch (entityType) { + case EntityType.USERS -> scopePermissions.getUsers().contains(identifier); + case EntityType.ROLES -> scopePermissions.getRoles().contains(identifier); + case EntityType.BACKEND_ROLES -> scopePermissions.getBackendRoles().contains(identifier); }; }) .orElse(false); // Return false if no matching scope is found diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index cce026b8be..bfc47a907e 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -405,7 +405,6 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set BoolQueryBuilder shouldQuery = QueryBuilders.boolQuery(); if ("*".equals(scope)) { - // Wildcard behavior: Match any scope dynamically for (String entity : entities) { shouldQuery.should( QueryBuilders.multiMatchQuery(entity, "share_with.*." + entityType + ".keyword") @@ -413,7 +412,6 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set ); } } else { - // Match the specific scope for (String entity : entities) { shouldQuery.should(QueryBuilders.termQuery("share_with." + scope + "." + entityType + ".keyword", entity)); } @@ -938,7 +936,6 @@ public ResourceSharing revokeAccess( } """, Map.of("revokeAccess", revoke, "scopes", scopesToUse)); - // Execute updateByQuery boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, revokeScript); return success ? fetchDocumentById(sourceIdx, resourceId) : null; From 5ad813bcb0d4b7e3516a5fdd29f6340db6e42bf4 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 7 Dec 2024 18:27:11 -0500 Subject: [PATCH 041/122] Adds validation for resource ownership when granting and revoking access Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessHandler.java | 8 +- .../ResourceSharingIndexHandler.java | 146 ++++++++++-------- 2 files changed, 85 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 74d83db8c1..d060ce6f38 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -19,7 +19,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.CreatedBy; import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; @@ -109,10 +108,9 @@ public boolean hasPermission(String resourceId, String systemIndexName, String s public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user, shareWith.toString()); + LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString()); - CreatedBy createdBy = new CreatedBy(user.getName()); - return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, createdBy, shareWith); + return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, user.getName(), shareWith); } public ResourceSharing revokeAccess( @@ -124,7 +122,7 @@ public ResourceSharing revokeAccess( final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes); - return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes); + return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes, user.getName()); } public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index bfc47a907e..6f07f608c9 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -148,7 +148,6 @@ public void createResourceSharingIndexIfAbsent(Callable callable) { public ResourceSharing indexResourceSharing(String resourceId, String resourceIndex, CreatedBy createdBy, ShareWith shareWith) throws IOException { - try { ResourceSharing entry = new ResourceSharing(resourceIndex, resourceId, createdBy, shareWith); @@ -516,7 +515,6 @@ public Set fetchDocumentsByField(String systemIndex, String field, Strin LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); return resourceIds; - } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); @@ -579,7 +577,6 @@ public Set fetchDocumentsByField(String systemIndex, String field, Strin */ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) { - // Input validation if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(resourceId)) { throw new IllegalArgumentException("systemIndexName and resourceId must not be null or empty"); } @@ -668,12 +665,13 @@ private void executeSearchRequest(Set resourceIds, Scroll scroll, Search /** * Updates the sharing configuration for an existing resource in the resource sharing index. - * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set)} + * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set, String)} * This method modifies the sharing permissions for a specific resource identified by its * resource ID and source index. * * @param resourceId The unique identifier of the resource whose sharing configuration needs to be updated * @param sourceIdx The source index where the original resource is stored + * @param requestUserName The user requesting to share the resource * @param shareWith Updated sharing configuration object containing access control settings: * { * "scope": { @@ -685,7 +683,7 @@ private void executeSearchRequest(Set resourceIds, Scroll scroll, Search * @return ResourceSharing Returns resourceSharing object if the update was successful, null otherwise * @throws RuntimeException if there's an error during the update operation */ - public ResourceSharing updateResourceSharingInfo(String resourceId, String sourceIdx, CreatedBy createdBy, ShareWith shareWith) { + public ResourceSharing updateResourceSharingInfo(String resourceId, String sourceIdx, String requestUserName, ShareWith shareWith) { XContentBuilder builder; Map shareWithMap; try { @@ -700,9 +698,22 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc return null; } + // Check if the user requesting to share is the owner of the resource + // TODO Add a way for users who are not creators to be able to share the resource + ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); + if (currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { + LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); + return null; + } + + CreatedBy createdBy; + if (currentSharingInfo == null) { + createdBy = new CreatedBy(requestUserName); + } else { + createdBy = currentSharingInfo.getCreatedBy(); + } + // Atomic operation - // TODO check if this script can be updated to replace magic identifiers (i.e. users, roles and backend_roles) with the ones - // supplied in shareWith Script updateScript = new Script(ScriptType.INLINE, "painless", """ if (ctx._source.share_with == null) { ctx._source.share_with = [:]; @@ -792,8 +803,8 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc * "query": { * "bool": { * "must": [ - * { "term": { "source_idx": sourceIdx } }, - * { "term": { "resource_id": resourceId } } + * { "term": { "source_idx.keyword": sourceIdx } }, + * { "term": { "resource_id.keyword": resourceId } } * ] * } * } @@ -810,8 +821,6 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId .setScript(updateScript) .setRefresh(true); - LOGGER.debug("Update By Query Request: {}", ubq.toString()); - BulkByScrollResponse response = client.execute(UpdateByQueryAction.INSTANCE, ubq).actionGet(); if (response.getUpdated() > 0) { @@ -866,6 +875,7 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * @param revokeAccess A map containing entity types (USER, ROLE, BACKEND_ROLE) and their corresponding * values to be removed from the sharing configuration * @param scopes A list of scopes to revoke access from. If null or empty, access is revoked from all scopes + * @param requestUserName The user trying to revoke the accesses * @return The updated ResourceSharing object after revoking access, or null if the document doesn't exist * @throws IllegalArgumentException if resourceId, sourceIdx is null/empty, or if revokeAccess is null/empty * @throws RuntimeException if the update operation fails or encounters an error @@ -887,12 +897,20 @@ public ResourceSharing revokeAccess( String resourceId, String sourceIdx, Map> revokeAccess, - Set scopes + Set scopes, + String requestUserName ) { if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(sourceIdx) || revokeAccess == null || revokeAccess.isEmpty()) { throw new IllegalArgumentException("resourceId, sourceIdx, and revokeAccess must not be null or empty"); } + // TODO Check if access can be revoked by non-creator + ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); + if (currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { + LOGGER.error("User {} is not authorized to revoke access to resource {}", requestUserName, resourceId); + return null; + } + LOGGER.debug("Revoking access for resource {} in {} for entities: {} and scopes: {}", resourceId, sourceIdx, revokeAccess, scopes); try { @@ -1019,58 +1037,58 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) } /** - * Deletes all resource sharing records that were created by a specific user. - * This method performs a delete-by-query operation to remove all documents where - * the created_by.user field matches the specified username. - * - *

    The method executes the following steps: - *

      - *
    1. Validates the input username parameter
    2. - *
    3. Creates a delete-by-query request with term query matching
    4. - *
    5. Executes the delete operation with immediate refresh
    6. - *
    7. Returns the operation status based on number of deleted documents
    8. - *
    - * - *

    Example query structure: - *

    -        * {
    -        *   "query": {
    -        *     "term": {
    -        *       "created_by.user": "username"
    -        *     }
    -        *   }
    -        * }
    -        * 
    - * - * @param name The username to match against the created_by.user field - * @return boolean indicating whether the deletion was successful: - *
      - *
    • true - if one or more documents were deleted
    • - *
    • false - if no documents were found
    • - *
    • false - if the operation failed due to an error
    • - *
    - * - * @throws IllegalArgumentException if name is null or empty - * - * - * @implNote Implementation details: - *
      - *
    • Uses DeleteByQueryRequest for efficient bulk deletion
    • - *
    • Sets refresh=true for immediate consistency
    • - *
    • Uses term query for exact username matching
    • - *
    • Implements comprehensive error handling and logging
    • - *
    - * - * Example usage: - *
    -        * boolean success = deleteAllRecordsForUser("john.doe");
    -        * if (success) {
    -        *     // Records were successfully deleted
    -        * } else {
    -        *     // No matching records found or operation failed
    -        * }
    -        * 
    - */ + * Deletes all resource sharing records that were created by a specific user. + * This method performs a delete-by-query operation to remove all documents where + * the created_by.user field matches the specified username. + * + *

    The method executes the following steps: + *

      + *
    1. Validates the input username parameter
    2. + *
    3. Creates a delete-by-query request with term query matching
    4. + *
    5. Executes the delete operation with immediate refresh
    6. + *
    7. Returns the operation status based on number of deleted documents
    8. + *
    + * + *

    Example query structure: + *

    +    * {
    +    *   "query": {
    +    *     "term": {
    +    *       "created_by.user": "username"
    +    *     }
    +    *   }
    +    * }
    +    * 
    + * + * @param name The username to match against the created_by.user field + * @return boolean indicating whether the deletion was successful: + *
      + *
    • true - if one or more documents were deleted
    • + *
    • false - if no documents were found
    • + *
    • false - if the operation failed due to an error
    • + *
    + * + * @throws IllegalArgumentException if name is null or empty + * + * + * @implNote Implementation details: + *
      + *
    • Uses DeleteByQueryRequest for efficient bulk deletion
    • + *
    • Sets refresh=true for immediate consistency
    • + *
    • Uses term query for exact username matching
    • + *
    • Implements comprehensive error handling and logging
    • + *
    + * + * Example usage: + *
    +    * boolean success = deleteAllRecordsForUser("john.doe");
    +    * if (success) {
    +    *     // Records were successfully deleted
    +    * } else {
    +    *     // No matching records found or operation failed
    +    * }
    +    * 
    + */ public boolean deleteAllRecordsForUser(String name) { if (StringUtils.isBlank(name)) { throw new IllegalArgumentException("Username must not be null or empty"); From 4dc7597d452f0b3d799e46bd95506cb061fbea9d Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 7 Dec 2024 18:28:27 -0500 Subject: [PATCH 042/122] Adds plugin specific exception class Signed-off-by: Darshit Chanpura --- .../access/ShareResourceTransportAction.java | 27 +++++++++---------- .../utils/SampleResourcePluginException.java | 17 ++++++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/utils/SampleResourcePluginException.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java index e99a9abf24..3288352d0b 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java @@ -21,6 +21,7 @@ import org.opensearch.sample.actions.access.share.ShareResourceAction; import org.opensearch.sample.actions.access.share.ShareResourceRequest; import org.opensearch.sample.actions.access.share.ShareResourceResponse; +import org.opensearch.sample.utils.SampleResourcePluginException; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -36,26 +37,24 @@ public ShareResourceTransportAction(TransportService transportService, ActionFil @Override protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { + ResourceSharing sharing = null; try { - shareResource(request); + sharing = shareResource(request); + if (sharing == null) { + log.error("Failed to share resource {}", request.getResourceId()); + SampleResourcePluginException se = new SampleResourcePluginException("Failed to share resource " + request.getResourceId()); + listener.onFailure(se); + return; + } + log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); } catch (Exception e) { listener.onFailure(e); } } - private void shareResource(ShareResourceRequest request) throws Exception { - try { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - ResourceSharing sharing = rs.getResourceAccessControlPlugin() - .shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, request.getShareWith()); - if (sharing == null) { - throw new Exception("Failed to share resource " + request.getResourceId()); - } - log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); - } catch (Exception e) { - log.info("Failed to share resource {}", request.getResourceId(), e); - throw e; - } + private ResourceSharing shareResource(ShareResourceRequest request) throws Exception { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + return rs.getResourceAccessControlPlugin().shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, request.getShareWith()); } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/SampleResourcePluginException.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/SampleResourcePluginException.java new file mode 100644 index 0000000000..1ac2baaaae --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/SampleResourcePluginException.java @@ -0,0 +1,17 @@ +/* + * 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. + */ + +package org.opensearch.sample.utils; + +import org.opensearch.OpenSearchException; + +public class SampleResourcePluginException extends OpenSearchException { + public SampleResourcePluginException(String msg, Object... args) { + super(msg, args); + } +} From 034953732f352d1a4cc4e87a71acbf6da197ea7a Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 7 Dec 2024 19:20:02 -0500 Subject: [PATCH 043/122] Fixes NPE Signed-off-by: Darshit Chanpura --- .../RevokeResourceAccessTransportAction.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java index dd7757e4f2..027e1fffe3 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java @@ -21,6 +21,7 @@ import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessAction; import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessRequest; import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessResponse; +import org.opensearch.sample.utils.SampleResourcePluginException; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -37,22 +38,25 @@ public RevokeResourceAccessTransportAction(TransportService transportService, Ac @Override protected void doExecute(Task task, RevokeResourceAccessRequest request, ActionListener listener) { try { - revokeAccess(request); + ResourceSharing revoke = revokeAccess(request); + if (revoke == null) { + log.error("Failed to revoke access to resource {}", request.getResourceId()); + SampleResourcePluginException se = new SampleResourcePluginException( + "Failed to revoke access to resource " + request.getResourceId() + ); + listener.onFailure(se); + return; + } + log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); listener.onResponse(new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.")); } catch (Exception e) { listener.onFailure(e); } } - private void revokeAccess(RevokeResourceAccessRequest request) { - try { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - ResourceSharing revoke = rs.getResourceAccessControlPlugin() - .revokeAccess(request.getResourceId(), RESOURCE_INDEX_NAME, request.getRevokeAccess(), request.getScopes()); - log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); - } catch (Exception e) { - log.info("Failed to revoke access for resource {}", request.getResourceId(), e); - throw e; - } + private ResourceSharing revokeAccess(RevokeResourceAccessRequest request) { + ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); + return rs.getResourceAccessControlPlugin() + .revokeAccess(request.getResourceId(), RESOURCE_INDEX_NAME, request.getRevokeAccess(), request.getScopes()); } } From c08a99273ccf3cd88d26f3421ba24c56599277c8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 9 Dec 2024 21:45:06 -0500 Subject: [PATCH 044/122] Adds super-admin bypass Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessHandler.java | 16 +++++++++++++-- .../ResourceSharingIndexHandler.java | 20 +++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index d060ce6f38..721692c85a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -82,8 +82,14 @@ public Set listAccessibleResourcesInPlugin(String pluginIndex) { public boolean hasPermission(String resourceId, String systemIndexName, String scope) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); + // check if user is admin, if yes the user has permission + if (adminDNs.isAdmin(user)) { + return true; + } + Set userRoles = user.getSecurityRoles(); Set userBackendRoles = user.getRoles(); @@ -110,7 +116,10 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString()); - return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, user.getName(), shareWith); + // check if user is admin, if yes the user has permission + boolean isAdmin = adminDNs.isAdmin(user); + + return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, user.getName(), shareWith, isAdmin); } public ResourceSharing revokeAccess( @@ -122,7 +131,10 @@ public ResourceSharing revokeAccess( final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes); - return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes, user.getName()); + // check if user is admin, if yes the user has permission + boolean isAdmin = adminDNs.isAdmin(user); + + return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes, user.getName(), isAdmin); } public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 6f07f608c9..a4ef90e492 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -145,7 +145,6 @@ public void createResourceSharingIndexIfAbsent(Callable callable) { * @return ResourceSharing Returns resourceSharing object if the operation was successful, null otherwise * @throws IOException if there are issues with index operations or JSON processing */ - public ResourceSharing indexResourceSharing(String resourceId, String resourceIndex, CreatedBy createdBy, ShareWith shareWith) throws IOException { try { @@ -665,7 +664,7 @@ private void executeSearchRequest(Set resourceIds, Scroll scroll, Search /** * Updates the sharing configuration for an existing resource in the resource sharing index. - * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set, String)} + * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set, String, boolean)} * This method modifies the sharing permissions for a specific resource identified by its * resource ID and source index. * @@ -680,10 +679,17 @@ private void executeSearchRequest(Set resourceIds, Scroll scroll, Search * "backend_roles": ["backend_role1"] * } * } + * @param isAdmin Boolean indicating whether the user requesting to share is an admin or not * @return ResourceSharing Returns resourceSharing object if the update was successful, null otherwise * @throws RuntimeException if there's an error during the update operation */ - public ResourceSharing updateResourceSharingInfo(String resourceId, String sourceIdx, String requestUserName, ShareWith shareWith) { + public ResourceSharing updateResourceSharingInfo( + String resourceId, + String sourceIdx, + String requestUserName, + ShareWith shareWith, + boolean isAdmin + ) { XContentBuilder builder; Map shareWithMap; try { @@ -701,7 +707,7 @@ public ResourceSharing updateResourceSharingInfo(String resourceId, String sourc // Check if the user requesting to share is the owner of the resource // TODO Add a way for users who are not creators to be able to share the resource ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); - if (currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { + if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); return null; } @@ -876,6 +882,7 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * values to be removed from the sharing configuration * @param scopes A list of scopes to revoke access from. If null or empty, access is revoked from all scopes * @param requestUserName The user trying to revoke the accesses + * @param isAdmin Boolean indicating whether the user is an admin or not * @return The updated ResourceSharing object after revoking access, or null if the document doesn't exist * @throws IllegalArgumentException if resourceId, sourceIdx is null/empty, or if revokeAccess is null/empty * @throws RuntimeException if the update operation fails or encounters an error @@ -898,7 +905,8 @@ public ResourceSharing revokeAccess( String sourceIdx, Map> revokeAccess, Set scopes, - String requestUserName + String requestUserName, + boolean isAdmin ) { if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(sourceIdx) || revokeAccess == null || revokeAccess.isEmpty()) { throw new IllegalArgumentException("resourceId, sourceIdx, and revokeAccess must not be null or empty"); @@ -906,7 +914,7 @@ public ResourceSharing revokeAccess( // TODO Check if access can be revoked by non-creator ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); - if (currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { + if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { LOGGER.error("User {} is not authorized to revoke access to resource {}", requestUserName, resourceId); return null; } From 8e3d41c2f24df3155cf02c474c2c2955f3ba70d6 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 11 Dec 2024 15:18:02 -0500 Subject: [PATCH 045/122] Updates method names and adds missing java doc Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 4 +- .../resources/ResourceAccessHandler.java | 155 ++++++++++++++---- .../ResourceSharingIndexHandler.java | 4 + .../ResourceSharingIndexListener.java | 21 +++ 4 files changed, 151 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 0e3e612e20..e0293a7abf 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2208,8 +2208,8 @@ private void tryAddSecurityProvider() { } @Override - public Set listAccessibleResourcesInPlugin(String systemIndexName) { - return this.resourceAccessHandler.listAccessibleResourcesInPlugin(systemIndexName); + public Set getAccessibleResourcesForCurrentUser(String systemIndexName) { + return this.resourceAccessHandler.getAccessibleResourcesForCurrentUser(systemIndexName); } @Override diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 721692c85a..5d5b39b697 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -29,6 +29,11 @@ import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; +/** + * This class handles resource access permissions for users and roles. + * It provides methods to check if a user has permission to access a resource + * based on the resource sharing configuration. + */ public class ResourceAccessHandler { private static final Logger LOGGER = LogManager.getLogger(ResourceAccessHandler.class); @@ -47,40 +52,54 @@ public ResourceAccessHandler( this.adminDNs = adminDns; } - public Set listAccessibleResourcesInPlugin(String pluginIndex) { + /** + * Returns a set of accessible resources for the current user within the specified resource index. + * + * @param resourceIndex The resource index to check for accessible resources. + * @return A set of accessible resource IDs. + */ + public Set getAccessibleResourcesForCurrentUser(String resourceIndex) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); return Collections.emptySet(); } - LOGGER.info("Listing accessible resource within a system index {} for : {}", pluginIndex, user.getName()); + LOGGER.info("Listing accessible resources within a resource index {} for : {}", resourceIndex, user.getName()); // check if user is admin, if yes all resources should be accessible if (adminDNs.isAdmin(user)) { - return loadAllResources(pluginIndex); + return loadAllResources(resourceIndex); } Set result = new HashSet<>(); // 0. Own resources - result.addAll(loadOwnResources(pluginIndex, user.getName())); + result.addAll(loadOwnResources(resourceIndex, user.getName())); // 1. By username - result.addAll(loadSharedWithResources(pluginIndex, Set.of(user.getName()), EntityType.USERS.toString())); + result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), EntityType.USERS.toString())); // 2. By roles Set roles = user.getSecurityRoles(); - result.addAll(loadSharedWithResources(pluginIndex, roles, EntityType.ROLES.toString())); + result.addAll(loadSharedWithResources(resourceIndex, roles, EntityType.ROLES.toString())); // 3. By backend_roles Set backendRoles = user.getRoles(); - result.addAll(loadSharedWithResources(pluginIndex, backendRoles, EntityType.BACKEND_ROLES.toString())); + result.addAll(loadSharedWithResources(resourceIndex, backendRoles, EntityType.BACKEND_ROLES.toString())); return result; } - public boolean hasPermission(String resourceId, String systemIndexName, String scope) { + /** + * Checks whether current user has given permission (scope) to access given resource. + * + * @param resourceId The resource ID to check access for. + * @param resourceIndex The resource index containing the resource. + * @param scope The permission scope to check. + * @return True if the user has the specified permission, false otherwise. + */ + public boolean hasPermission(String resourceId, String resourceIndex, String scope) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); @@ -93,9 +112,9 @@ public boolean hasPermission(String resourceId, String systemIndexName, String s Set userRoles = user.getSecurityRoles(); Set userBackendRoles = user.getRoles(); - ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId); + ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId); if (document == null) { - LOGGER.warn("Resource {} not found in index {}", resourceId, systemIndexName); + LOGGER.warn("Resource {} not found in index {}", resourceId, resourceIndex); return false; // If the document doesn't exist, no permissions can be granted } @@ -112,19 +131,34 @@ public boolean hasPermission(String resourceId, String systemIndexName, String s return false; } - public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { + /** + * Shares a resource with the specified users, roles, and backend roles. + * @param resourceId The resource ID to share. + * @param resourceIndex The index where resource is store + * @param shareWith The users, roles, and backend roles as well as scope to share the resource with. + * @return The updated ResourceSharing document. + */ + public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString()); // check if user is admin, if yes the user has permission boolean isAdmin = adminDNs.isAdmin(user); - return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, systemIndexName, user.getName(), shareWith, isAdmin); + return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, resourceIndex, user.getName(), shareWith, isAdmin); } + /** + * Revokes access to a resource for the specified users, roles, and backend roles. + * @param resourceId The resource ID to revoke access from. + * @param resourceIndex The index where resource is store + * @param revokeAccess The users, roles, and backend roles to revoke access for. + * @param scopes The permission scopes to revoke access for. + * @return The updated ResourceSharing document. + */ public ResourceSharing revokeAccess( String resourceId, - String systemIndexName, + String resourceIndex, Map> revokeAccess, Set scopes ) { @@ -134,25 +168,35 @@ public ResourceSharing revokeAccess( // check if user is admin, if yes the user has permission boolean isAdmin = adminDNs.isAdmin(user); - return this.resourceSharingIndexHandler.revokeAccess(resourceId, systemIndexName, revokeAccess, scopes, user.getName(), isAdmin); + return this.resourceSharingIndexHandler.revokeAccess(resourceId, resourceIndex, revokeAccess, scopes, user.getName(), isAdmin); } - public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { + /** + * Deletes a resource sharing record by its ID and the resource index it belongs to. + * @param resourceId The resource ID to delete. + * @param resourceIndex The resource index containing the resource. + * @return True if the record was successfully deleted, false otherwise. + */ + public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); - LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, systemIndexName, user.getName()); + LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, resourceIndex, user.getName()); - ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(systemIndexName, resourceId); + ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId); if (document == null) { - LOGGER.info("Document {} does not exist in index {}", resourceId, systemIndexName); + LOGGER.info("Document {} does not exist in index {}", resourceId, resourceIndex); return false; } if (!(adminDNs.isAdmin(user) || isOwnerOfResource(document, user.getName()))) { LOGGER.info("User {} does not have access to delete the record {} ", user.getName(), resourceId); return false; } - return this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, systemIndexName); + return this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex); } + /** + * Deletes all resource sharing records for the current user. + * @return True if all records were successfully deleted, false otherwise. + */ public boolean deleteAllResourceSharingRecordsForCurrentUser() { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); @@ -160,39 +204,88 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() { return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName()); } - // Helper methods - - private Set loadAllResources(String systemIndex) { - return this.resourceSharingIndexHandler.fetchAllDocuments(systemIndex); + /** + * Loads all resources within the specified resource index. + * + * @param resourceIndex The resource index to load resources from. + * @return A set of resource IDs. + */ + private Set loadAllResources(String resourceIndex) { + return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex); } - private Set loadOwnResources(String systemIndex, String username) { - // TODO check if this magic variable can be replaced - return this.resourceSharingIndexHandler.fetchDocumentsByField(systemIndex, "created_by.user", username); + /** + * Loads resources owned by the specified user within the given resource index. + * + * @param resourceIndex The resource index to load resources from. + * @param userName The username of the owner. + * @return A set of resource IDs owned by the user. + */ + private Set loadOwnResources(String resourceIndex, String userName) { + return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName); } - private Set loadSharedWithResources(String systemIndex, Set entities, String shareWithType) { - return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(systemIndex, entities, shareWithType); + /** + * Loads resources shared with the specified entities within the given resource index. + * + * @param resourceIndex The resource index to load resources from. + * @param entities The set of entities to check for shared resources. + * @param entityType The type of entity (e.g., users, roles, backend_roles). + * @return A set of resource IDs shared with the specified entities. + */ + private Set loadSharedWithResources(String resourceIndex, Set entities, String entityType) { + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, entityType); } + /** + * Checks if the given resource is owned by the specified user. + * + * @param document The ResourceSharing document to check. + * @param userName The username to check ownership against. + * @return True if the resource is owned by the user, false otherwise. + */ private boolean isOwnerOfResource(ResourceSharing document, String userName) { return document.getCreatedBy() != null && document.getCreatedBy().getUser().equals(userName); } - private boolean isSharedWithEntity(ResourceSharing document, EntityType entityType, Set roles, String scope) { - for (String role : roles) { - if (checkSharing(document, entityType, role, scope)) { + /** + * Checks if the given resource is shared with the specified entities and scope. + * + * @param document The ResourceSharing document to check. + * @param entityType The type of entity (e.g., users, roles, backend_roles). + * @param entities The set of entities to check for sharing. + * @param scope The permission scope to check. + * @return True if the resource is shared with the entities and scope, false otherwise. + */ + private boolean isSharedWithEntity(ResourceSharing document, EntityType entityType, Set entities, String scope) { + for (String entity : entities) { + if (checkSharing(document, entityType, entity, scope)) { return true; } } return false; } + /** + * Checks if the given resource is shared with everyone. + * + * @param document The ResourceSharing document to check. + * @return True if the resource is shared with everyone, false otherwise. + */ private boolean isSharedWithEveryone(ResourceSharing document) { return document.getShareWith() != null && document.getShareWith().getSharedWithScopes().stream().anyMatch(sharedWithScope -> sharedWithScope.getScope().equals("*")); } + /** + * Checks if the given resource is shared with the specified entity and scope. + * + * @param document The ResourceSharing document to check. + * @param entityType The type of entity (e.g., users, roles, backend_roles). + * @param identifier The identifier of the entity to check for sharing. + * @param scope The permission scope to check. + * @return True if the resource is shared with the entity and scope, false otherwise. + */ private boolean checkSharing(ResourceSharing document, EntityType entityType, String identifier, String scope) { if (document.getShareWith() == null) { return false; diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index a4ef90e492..ec75515985 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -67,6 +67,10 @@ import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; +/** + * This class handles the creation and management of the resource sharing index. + * It provides methods to create the index, index resource sharing entries along with updates and deletion, retrieve shared resources. + */ public class ResourceSharingIndexHandler { private static final Logger LOGGER = LogManager.getLogger(ResourceSharingIndexHandler.class); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index d7b149a2fb..2fad56fc1b 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -46,6 +46,13 @@ public static ResourceSharingIndexListener getInstance() { } + /** + * Initializes the ResourceSharingIndexListener with the provided ThreadPool and Client. + * This method is called during the plugin's initialization process. + * + * @param threadPool The ThreadPool instance to be used for executing operations. + * @param client The Client instance to be used for interacting with OpenSearch. + */ public void initialize(ThreadPool threadPool, Client client) { if (initialized) { @@ -66,6 +73,13 @@ public boolean isInitialized() { return initialized; } + /** + * This method is called after an index operation is performed. + * It creates a resource sharing entry in the dedicated resource sharing index. + * @param shardId The shard ID of the index where the operation was performed. + * @param index The index where the operation was performed. + * @param result The result of the index operation. + */ @Override public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult result) { @@ -89,6 +103,13 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re } } + /** + * This method is called after a delete operation is performed. + * It deletes the corresponding resource sharing entry from the dedicated resource sharing index. + * @param shardId The shard ID of the index where the delete operation was performed. + * @param delete The delete operation that was performed. + * @param result The result of the delete operation. + */ @Override public void postDelete(ShardId shardId, Engine.Delete delete, Engine.DeleteResult result) { From 334b50d438853d61b5409abc77de190151b01cc2 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 11 Dec 2024 15:38:51 -0500 Subject: [PATCH 046/122] Updates method name to corresponding to changes in core Signed-off-by: Darshit Chanpura --- sample-resource-plugin/README.md | 146 ++++++++++++++++++ .../sample/SampleResourcePlugin.java | 5 +- ...istAccessibleResourcesTransportAction.java | 2 +- 3 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 sample-resource-plugin/README.md diff --git a/sample-resource-plugin/README.md b/sample-resource-plugin/README.md new file mode 100644 index 0000000000..d40d792f68 --- /dev/null +++ b/sample-resource-plugin/README.md @@ -0,0 +1,146 @@ +# Resource Sharing and Access Control Plugin + +This plugin demonstrates resource sharing and access control functionality, providing APIs to create, manage, and verify access to resources. The plugin enables fine-grained permissions for sharing and accessing resources, making it suitable for systems requiring robust security and collaboration. + +## Features + +- Create and delete resources. +- Share resources with specific users, roles and/or backend_roles with specific scope(s). +- Revoke access to shared resources for a list of or all scopes. +- Verify access permissions for a given user within a given scope. +- List all resources accessible to current user. + +## API Endpoints + +The plugin exposes the following six API endpoints: + +### 1. Create Resource +- **Endpoint:** `POST /_plugins/sample_resource_sharing/create` +- **Description:** Creates a new resource. Also creates a resource sharing entry if security plugin is enabled. +- **Request Body:** + ```json + { + "name": "" + } + ``` +- **Response:** + ```json + { + "resource_id": "", + "status": "created" + } + ``` + +### 2. Delete Resource +- **Endpoint:** `DELETE /api/resource/{resource_id}` +- **Description:** Deletes a specified resource owned by the requesting user. +- **Response:** + ```json + { + "resource_id": "", + "status": "deleted" + } + ``` + +### 3. Share Resource +- **Endpoint:** `POST /api/resource/{resource_id}/share` +- **Description:** Shares a resource with specified users or roles with defined permissions. +- **Request Body:** + ```json + { + "share_with": [ + { "type": "user", "id": "user123", "permission": "read_write" }, + { "type": "role", "id": "admin", "permission": "read_only" } + ] + } + ``` +- **Response:** + ```json + { + "resource_id": "", + "status": "shared" + } + ``` + +### 4. Revoke Access +- **Endpoint:** `DELETE /api/resource/{resource_id}/revoke` +- **Description:** Revokes access to a resource for specified users or roles. +- **Request Body:** + ```json + { + "revoke_from": [ "user123", "role:admin" ] + } + ``` +- **Response:** + ```json + { + "resource_id": "", + "status": "access_revoked" + } + ``` + +### 5. Verify Access +- **Endpoint:** `GET /api/resource/{resource_id}/verify` +- **Description:** Verifies if a user or role has access to a specific resource. +- **Query Parameters:** + - `user_id` (optional): ID of the user. + - `role` (optional): Role to verify. +- **Response:** + ```json + { + "resource_id": "", + "access": true, + "permissions": "read_only" + } + ``` + +### 6. List Accessible Resources +- **Endpoint:** `GET /api/resources/accessible` +- **Description:** Lists all resources accessible to the requesting user or role. +- **Response:** + ```json + [ + { + "resource_id": "", + "name": "", + "permissions": "read_write" + }, + { + "resource_id": "", + "name": "", + "permissions": "read_only" + } + ] + ``` + +## Installation + +1. Clone the repository: + ```bash + git clone + ``` + +2. Navigate to the project directory: + ```bash + cd resource-access-plugin + ``` + +3. Build and deploy the plugin: + ```bash + + ``` + +4. Configure the plugin in your environment. + +## Configuration + +- Ensure that the appropriate access control settings are enabled in your system. +- Define user roles and permissions to match your use case. + +## License + +This code is licensed under the Apache 2.0 License. + +## Copyright + +Copyright OpenSearch Contributors. diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 90a62f7286..3119e2203a 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -76,8 +76,6 @@ public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); - private Client client; - @Override public Collection createComponents( Client client, @@ -92,7 +90,6 @@ public Collection createComponents( IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier ) { - this.client = client; log.info("Loaded SampleResourcePlugin components."); return Collections.emptyList(); } @@ -131,7 +128,7 @@ public List getRestHandlers( @Override public Collection getSystemIndexDescriptors(Settings settings) { - final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Example index with resources"); + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Sample index with resources"); return Collections.singletonList(systemIndexDescriptor); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java index 2ca748c7d5..2c021d6c27 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java @@ -41,7 +41,7 @@ public ListAccessibleResourcesTransportAction(TransportService transportService, protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - Set resourceIds = rs.getResourceAccessControlPlugin().listAccessibleResourcesInPlugin(RESOURCE_INDEX_NAME); + Set resourceIds = rs.getResourceAccessControlPlugin().getAccessibleResourcesForCurrentUser(RESOURCE_INDEX_NAME); log.info("Successfully fetched accessible resources for current user : {}", resourceIds); listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); } catch (Exception e) { From 0f60c917a319356490fd277ef3228edec1ec7d5a Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 11 Dec 2024 16:09:31 -0500 Subject: [PATCH 047/122] Updates API route Signed-off-by: Darshit Chanpura --- .../actions/access/list/ListAccessibleResourcesRestAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java index 2eee67e0f1..c387eacf90 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java @@ -24,7 +24,7 @@ public ListAccessibleResourcesRestAction() {} @Override public List routes() { - return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/resource")); + return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/list")); } @Override From 5ca5dec141e1a5aa0e2d05f84705db9eb023b0d7 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 11 Dec 2024 16:09:44 -0500 Subject: [PATCH 048/122] Adds README Signed-off-by: Darshit Chanpura --- sample-resource-plugin/README.md | 118 ++++++++++++++++--------------- 1 file changed, 62 insertions(+), 56 deletions(-) diff --git a/sample-resource-plugin/README.md b/sample-resource-plugin/README.md index d40d792f68..ccd73db983 100644 --- a/sample-resource-plugin/README.md +++ b/sample-resource-plugin/README.md @@ -26,117 +26,123 @@ The plugin exposes the following six API endpoints: - **Response:** ```json { - "resource_id": "", - "status": "created" + "message": "Resource created successfully." } ``` ### 2. Delete Resource -- **Endpoint:** `DELETE /api/resource/{resource_id}` +- **Endpoint:** `DELETE /_plugins/sample_resource_sharing/{resource_id}` - **Description:** Deletes a specified resource owned by the requesting user. - **Response:** ```json { - "resource_id": "", - "status": "deleted" + "message": "Resource deleted successfully." } ``` ### 3. Share Resource -- **Endpoint:** `POST /api/resource/{resource_id}/share` -- **Description:** Shares a resource with specified users or roles with defined permissions. +- **Endpoint:** `POST /_plugins/sample_resource_sharing/share` +- **Description:** Shares a resource with specified users or roles with defined scope. - **Request Body:** ```json - { - "share_with": [ - { "type": "user", "id": "user123", "permission": "read_write" }, - { "type": "role", "id": "admin", "permission": "read_only" } - ] - } + { + "resource_id" : "{{ADMIN_RESOURCE_ID}}", + "share_with" : { + "SAMPLE_FULL_ACCESS": { + "users": ["test"], + "roles": ["test_role"], + "backend_roles": ["test_backend_role"] + }, + "READ_ONLY": { + "users": ["test"], + "roles": ["test_role"], + "backend_roles": ["test_backend_role"] + }, + "READ_WRITE": { + "users": ["test"], + "roles": ["test_role"], + "backend_roles": ["test_backend_role"] + } + } + } ``` - **Response:** ```json - { - "resource_id": "", - "status": "shared" - } + { + "message": "Resource shared successfully." + } ``` ### 4. Revoke Access -- **Endpoint:** `DELETE /api/resource/{resource_id}/revoke` +- **Endpoint:** `POST /_plugins/sample_resource_sharing/revoke` - **Description:** Revokes access to a resource for specified users or roles. - **Request Body:** ```json - { - "revoke_from": [ "user123", "role:admin" ] - } + { + "resource_id" : "", + "entities" : { + "users": ["test", "admin"], + "roles": ["test_role", "all_access"], + "backend_roles": ["test_backend_role", "admin"] + }, + "scopes": ["SAMPLE_FULL_ACCESS", "READ_ONLY", "READ_WRITE"] + } ``` - **Response:** ```json - { - "resource_id": "", - "status": "access_revoked" - } + { + "message": "Resource access revoked successfully." + } ``` ### 5. Verify Access -- **Endpoint:** `GET /api/resource/{resource_id}/verify` -- **Description:** Verifies if a user or role has access to a specific resource. -- **Query Parameters:** - - `user_id` (optional): ID of the user. - - `role` (optional): Role to verify. +- **Endpoint:** `GET /_plugins/sample_resource_sharing/verify_resource_access` +- **Description:** Verifies if a user or role has access to a specific resource with a specific scope. +- **Request Body:** + ```json + { + "resource_id": "", + "scope": "SAMPLE_FULL_ACCESS" + } + ``` - **Response:** ```json { - "resource_id": "", - "access": true, - "permissions": "read_only" + "message": "User has requested scope SAMPLE_FULL_ACCESS access to " } ``` ### 6. List Accessible Resources -- **Endpoint:** `GET /api/resources/accessible` +- **Endpoint:** `GET /_plugins/sample_resource_sharing/list` - **Description:** Lists all resources accessible to the requesting user or role. - **Response:** ```json - [ - { - "resource_id": "", - "name": "", - "permissions": "read_write" - }, - { - "resource_id": "", - "name": "", - "permissions": "read_only" - } - ] + { + "resource-ids": [ + "", + "" + ] + } ``` ## Installation 1. Clone the repository: ```bash - git clone + git clone git@github.com:opensearch-project/security.git ``` 2. Navigate to the project directory: ```bash - cd resource-access-plugin + cd sample-resource-plugin ``` 3. Build and deploy the plugin: ```bash - + $ ./gradlew clean build -x test -x integrationTest -x spotbugsIntegrationTest + $ ./bin/opensearch-plugin install file: /sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-3.0.0.0-SNAPSHOT.zip ``` -4. Configure the plugin in your environment. - -## Configuration - -- Ensure that the appropriate access control settings are enabled in your system. -- Define user roles and permissions to match your use case. - ## License This code is licensed under the Apache 2.0 License. From cabbcd60d557103c263f56412c79600a4773a423 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 12 Dec 2024 16:48:28 -0500 Subject: [PATCH 049/122] Updates methods to return actual resources instead of resource ids Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 4 +- .../resources/ResourceAccessHandler.java | 26 +++---- .../ResourceSharingIndexHandler.java | 74 ++++++++++++++----- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e0293a7abf..118b5bc88b 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2208,8 +2208,8 @@ private void tryAddSecurityProvider() { } @Override - public Set getAccessibleResourcesForCurrentUser(String systemIndexName) { - return this.resourceAccessHandler.getAccessibleResourcesForCurrentUser(systemIndexName); + public Set getAccessibleResourcesForCurrentUser(String systemIndexName, Class clazz) { + return this.resourceAccessHandler.getAccessibleResourcesForCurrentUser(systemIndexName, clazz); } @Override diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 5d5b39b697..6837f88cbf 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -58,7 +58,7 @@ public ResourceAccessHandler( * @param resourceIndex The resource index to check for accessible resources. * @return A set of accessible resource IDs. */ - public Set getAccessibleResourcesForCurrentUser(String resourceIndex) { + public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); @@ -69,24 +69,24 @@ public Set getAccessibleResourcesForCurrentUser(String resourceIndex) { // check if user is admin, if yes all resources should be accessible if (adminDNs.isAdmin(user)) { - return loadAllResources(resourceIndex); + return loadAllResources(resourceIndex, clazz); } - Set result = new HashSet<>(); + Set result = new HashSet<>(); // 0. Own resources - result.addAll(loadOwnResources(resourceIndex, user.getName())); + result.addAll(loadOwnResources(resourceIndex, user.getName(), clazz)); // 1. By username - result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), EntityType.USERS.toString())); + result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), EntityType.USERS.toString(), clazz)); // 2. By roles Set roles = user.getSecurityRoles(); - result.addAll(loadSharedWithResources(resourceIndex, roles, EntityType.ROLES.toString())); + result.addAll(loadSharedWithResources(resourceIndex, roles, EntityType.ROLES.toString(), clazz)); // 3. By backend_roles Set backendRoles = user.getRoles(); - result.addAll(loadSharedWithResources(resourceIndex, backendRoles, EntityType.BACKEND_ROLES.toString())); + result.addAll(loadSharedWithResources(resourceIndex, backendRoles, EntityType.BACKEND_ROLES.toString(), clazz)); return result; } @@ -210,8 +210,8 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() { * @param resourceIndex The resource index to load resources from. * @return A set of resource IDs. */ - private Set loadAllResources(String resourceIndex) { - return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex); + private Set loadAllResources(String resourceIndex, Class clazz) { + return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, clazz); } /** @@ -221,8 +221,8 @@ private Set loadAllResources(String resourceIndex) { * @param userName The username of the owner. * @return A set of resource IDs owned by the user. */ - private Set loadOwnResources(String resourceIndex, String userName) { - return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName); + private Set loadOwnResources(String resourceIndex, String userName, Class clazz) { + return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, clazz); } /** @@ -233,8 +233,8 @@ private Set loadOwnResources(String resourceIndex, String userName) { * @param entityType The type of entity (e.g., users, roles, backend_roles). * @return A set of resource IDs shared with the specified entities. */ - private Set loadSharedWithResources(String resourceIndex, Set entities, String entityType) { - return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, entityType); + private Set loadSharedWithResources(String resourceIndex, Set entities, String entityType, Class clazz) { + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, entityType, clazz); } /** diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index ec75515985..e53c1f1a56 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -30,6 +30,9 @@ import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; +import org.opensearch.action.get.MultiGetItemResponse; +import org.opensearch.action.get.MultiGetRequest; +import org.opensearch.action.get.MultiGetResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.action.search.ClearScrollRequest; @@ -214,7 +217,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn *
  • Returns an empty list instead of throwing exceptions
  • * */ - public Set fetchAllDocuments(String pluginIndex) { + public Set fetchAllDocuments(String pluginIndex, Class clazz) { LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); try { @@ -242,7 +245,7 @@ public Set fetchAllDocuments(String pluginIndex) { LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); - return resourceIds; + return getResourcesFromIds(resourceIds, pluginIndex, clazz); } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); @@ -316,9 +319,9 @@ public Set fetchAllDocuments(String pluginIndex) { * */ - public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType) { + public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType, Class clazz) { // "*" must match all scopes - return fetchDocumentsForAGivenScope(pluginIndex, entities, entityType, "*"); + return fetchDocumentsForAGivenScope(pluginIndex, entities, entityType, "*", clazz); } /** @@ -387,7 +390,13 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set en *
  • Properly cleans up scroll context after use
  • * */ - public Set fetchDocumentsForAGivenScope(String pluginIndex, Set entities, String entityType, String scope) { + public Set fetchDocumentsForAGivenScope( + String pluginIndex, + Set entities, + String entityType, + String scope, + Class clazz + ) { LOGGER.debug( "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", pluginIndex, @@ -426,11 +435,11 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); - return resourceIds; + return getResourcesFromIds(resourceIds, pluginIndex, clazz); } catch (Exception e) { LOGGER.error( - "Failed to fetch documents from {} for criteria - systemIndex: {}, scope: {}, entityType: {}, entities: {}", + "Failed to fetch documents from {} for criteria - pluginIndex: {}, scope: {}, entityType: {}, entities: {}", resourceSharingIndex, pluginIndex, scope, @@ -472,7 +481,7 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set * } * * - * @param systemIndex The source index to match against the source_idx field + * @param pluginIndex The source index to match against the source_idx field * @param field The field name to search in. Must be a valid field in the index mapping * @param value The value to match for the specified field. Performs exact term matching * @return Set List of resource IDs that match the criteria. Returns an empty list @@ -495,12 +504,12 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set * Set resources = fetchDocumentsByField("myIndex", "status", "active"); * */ - public Set fetchDocumentsByField(String systemIndex, String field, String value) { - if (StringUtils.isBlank(systemIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { - throw new IllegalArgumentException("systemIndex, field, and value must not be null or empty"); + public Set fetchDocumentsByField(String pluginIndex, String field, String value, Class clazz) { + if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { + throw new IllegalArgumentException("pluginIndex, field, and value must not be null or empty"); } - LOGGER.debug("Fetching documents from index: {}, where {} = {}", systemIndex, field, value); + LOGGER.debug("Fetching documents from index: {}, where {} = {}", pluginIndex, field, value); Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); @@ -510,14 +519,14 @@ public Set fetchDocumentsByField(String systemIndex, String field, Strin searchRequest.scroll(scroll); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("source_idx.keyword", systemIndex)) + .must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)) .must(QueryBuilders.termQuery(field + ".keyword", value)); executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery); LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); - return resourceIds; + return getResourcesFromIds(resourceIds, pluginIndex, clazz); } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); @@ -557,7 +566,7 @@ public Set fetchDocumentsByField(String systemIndex, String field, Strin * @return ResourceSharing object if a matching document is found, null if no document * matches the criteria * - * @throws IllegalArgumentException if systemIndexName or resourceId is null or empty + * @throws IllegalArgumentException if pluginIndexName or resourceId is null or empty * @throws RuntimeException if the search operation fails or parsing errors occur, * wrapping the underlying exception * @@ -581,7 +590,7 @@ public Set fetchDocumentsByField(String systemIndex, String field, Strin public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) { if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(resourceId)) { - throw new IllegalArgumentException("systemIndexName and resourceId must not be null or empty"); + throw new IllegalArgumentException("pluginIndexName and resourceId must not be null or empty"); } LOGGER.debug("Fetching document from index: {}, with resourceId: {}", pluginIndex, resourceId); @@ -901,7 +910,7 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * Map> revokeAccess = new HashMap<>(); * revokeAccess.put(EntityType.USER, Set.of("user1", "user2")); * revokeAccess.put(EntityType.ROLE, Set.of("role1")); - * ResourceSharing updated = revokeAccess("resourceId", "systemIndex", revokeAccess); + * ResourceSharing updated = revokeAccess("resourceId", "pluginIndex", revokeAccess); * */ public ResourceSharing revokeAccess( @@ -1131,4 +1140,35 @@ public boolean deleteAllRecordsForUser(String name) { } } + /** + * Fetches all documents from the specified resource index and deserializes them into the specified class. + * + * @param resourceIndex The resource index to fetch documents from. + * @param clazz The class to deserialize the documents into. + * @return A set of deserialized documents. + */ + private Set getResourcesFromIds(Set resourceIds, String resourceIndex, Class clazz) { + + Set result = new HashSet<>(); + try { + MultiGetRequest request = new MultiGetRequest(); + for (String id : resourceIds) { + request.add(new MultiGetRequest.Item(resourceIndex, id)); + } + + MultiGetResponse response = client.multiGet(request).actionGet(); + + for (MultiGetItemResponse itemResponse : response.getResponses()) { + if (!itemResponse.isFailed() && itemResponse.getResponse().isExists()) { + String sourceAsString = itemResponse.getResponse().getSourceAsString(); + T resource = DefaultObjectMapper.readValue(sourceAsString, clazz); + result.add(resource); + } + } + } catch (Exception e) { + LOGGER.error("Failed to fetch resources with ids {} from index {}", resourceIds, resourceIndex, e); + } + + return result; + } } From ca377f6311b6b9adc0d44baeac3a8184c9c3d459 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 12 Dec 2024 17:25:45 -0500 Subject: [PATCH 050/122] Returns actual resource when listing the resource Signed-off-by: Darshit Chanpura --- .../resource/create => }/SampleResource.java | 5 ++--- .../list/ListAccessibleResourcesResponse.java | 13 +++++++------ .../resource/create/CreateResourceRestAction.java | 1 + .../ListAccessibleResourcesTransportAction.java | 8 +++++--- 4 files changed, 15 insertions(+), 12 deletions(-) rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource/create => }/SampleResource.java (90%) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java similarity index 90% rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/SampleResource.java rename to sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java index db475b7018..07441d96b8 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java @@ -9,14 +9,13 @@ * GitHub history for details. */ -package org.opensearch.sample.actions.resource.create; +package org.opensearch.sample; import java.io.IOException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.sample.Resource; import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; @@ -26,7 +25,7 @@ public class SampleResource implements Resource { public SampleResource() {} - SampleResource(StreamInput in) throws IOException { + public SampleResource(StreamInput in) throws IOException { this.name = in.readString(); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java index fb1112bc1d..9c5d2a3e8a 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java @@ -16,30 +16,31 @@ import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.sample.SampleResource; /** * Response to a ListAccessibleResourcesRequest */ public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { - private final Set resourceIds; + private final Set resources; - public ListAccessibleResourcesResponse(Set resourceIds) { - this.resourceIds = resourceIds; + public ListAccessibleResourcesResponse(Set resources) { + this.resources = resources; } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeStringArray(resourceIds.toArray(new String[0])); + out.writeCollection(resources); } public ListAccessibleResourcesResponse(final StreamInput in) throws IOException { - resourceIds = in.readSet(StreamInput::readString); + this.resources = in.readSet(SampleResource::new); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - builder.field("resource-ids", resourceIds); + builder.field("resources", resources); builder.endObject(); return builder; } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java index 171c539a7c..f56f61d010 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java @@ -17,6 +17,7 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.sample.SampleResource; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.POST; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java index 2c021d6c27..57c2c7889f 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java @@ -18,6 +18,7 @@ import org.opensearch.action.support.HandledTransportAction; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; +import org.opensearch.sample.SampleResource; import org.opensearch.sample.SampleResourcePlugin; import org.opensearch.sample.actions.access.list.ListAccessibleResourcesAction; import org.opensearch.sample.actions.access.list.ListAccessibleResourcesRequest; @@ -41,9 +42,10 @@ public ListAccessibleResourcesTransportAction(TransportService transportService, protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - Set resourceIds = rs.getResourceAccessControlPlugin().getAccessibleResourcesForCurrentUser(RESOURCE_INDEX_NAME); - log.info("Successfully fetched accessible resources for current user : {}", resourceIds); - listener.onResponse(new ListAccessibleResourcesResponse(resourceIds)); + Set resources = rs.getResourceAccessControlPlugin() + .getAccessibleResourcesForCurrentUser(RESOURCE_INDEX_NAME, SampleResource.class); + log.info("Successfully fetched accessible resources for current user : {}", resources); + listener.onResponse(new ListAccessibleResourcesResponse(resources)); } catch (Exception e) { log.info("Failed to list accessible resources for current user: ", e); listener.onFailure(e); From dc964aca7eaa375377b04c6d7354557a31202264 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 12 Dec 2024 20:33:27 -0500 Subject: [PATCH 051/122] Stash context to fetch resources from a system index Signed-off-by: Darshit Chanpura --- .../security/resources/ResourceSharingIndexHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index e53c1f1a56..92ef31402a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -1150,7 +1150,8 @@ public boolean deleteAllRecordsForUser(String name) { private Set getResourcesFromIds(Set resourceIds, String resourceIndex, Class clazz) { Set result = new HashSet<>(); - try { + // stashing Context to avoid permission issues in-case resourceIndex is a system index + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { MultiGetRequest request = new MultiGetRequest(); for (String id : resourceIds) { request.add(new MultiGetRequest.Item(resourceIndex, id)); From cc973c6864be903f5acba05c28834cce42d6a248 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 12 Dec 2024 20:55:43 -0500 Subject: [PATCH 052/122] Optimize call to fetch resources Signed-off-by: Darshit Chanpura --- .../security/resources/ResourceSharingIndexHandler.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 92ef31402a..cf622b46a1 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -245,7 +245,7 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); - return getResourcesFromIds(resourceIds, pluginIndex, clazz); + return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, clazz); } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); @@ -435,7 +435,7 @@ public Set fetchDocumentsForAGivenScope( LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); - return getResourcesFromIds(resourceIds, pluginIndex, clazz); + return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, clazz); } catch (Exception e) { LOGGER.error( @@ -526,7 +526,7 @@ public Set fetchDocumentsByField(String pluginIndex, String field, String LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); - return getResourcesFromIds(resourceIds, pluginIndex, clazz); + return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, clazz); } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); From 3ce3d92735a927b78e641b155a1c6104a61e781b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 13 Dec 2024 01:28:44 -0500 Subject: [PATCH 053/122] Updates javadoc Signed-off-by: Darshit Chanpura --- .../security/resources/ResourceSharingIndexHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index cf622b46a1..c0f6ea2bd0 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -304,6 +304,7 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { *
  • "roles" - for role-based access
  • *
  • "backend_roles" - for backend role-based access
  • * + * @param clazz Class to deserialize each document from Response into * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found * @@ -376,6 +377,7 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent *
  • "backend_roles" - for backend role-based access
  • * * @param scope The scope of the access. Should be implementation of {@link org.opensearch.accesscontrol.resources.ResourceAccessScope} + * @param clazz Class to deserialize each document from Response into * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found * @@ -484,6 +486,7 @@ public Set fetchDocumentsForAGivenScope( * @param pluginIndex The source index to match against the source_idx field * @param field The field name to search in. Must be a valid field in the index mapping * @param value The value to match for the specified field. Performs exact term matching + * @param clazz Class to deserialize each document from Response into * @return Set List of resource IDs that match the criteria. Returns an empty list * if no matches are found * From 428e11e204492d91c91161ee5b6aa7838abb28f3 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 13 Dec 2024 01:43:22 -0500 Subject: [PATCH 054/122] Adds input validation Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessHandler.java | 32 +++++++++++++++++++ .../ResourceSharingIndexHandler.java | 1 - 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 6837f88cbf..41b999c009 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -59,6 +59,9 @@ public ResourceAccessHandler( * @return A set of accessible resource IDs. */ public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { + if (areArgumentsInvalid(resourceIndex, clazz)) { + return Collections.emptySet(); + } final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); @@ -100,6 +103,9 @@ public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Cla * @return True if the user has the specified permission, false otherwise. */ public boolean hasPermission(String resourceId, String resourceIndex, String scope) { + if (areArgumentsInvalid(resourceId, resourceIndex, scope)) { + return false; + } final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); @@ -139,6 +145,9 @@ public boolean hasPermission(String resourceId, String resourceIndex, String sco * @return The updated ResourceSharing document. */ public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) { + if (areArgumentsInvalid(resourceId, resourceIndex, shareWith)) { + return null; + } final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString()); @@ -162,6 +171,9 @@ public ResourceSharing revokeAccess( Map> revokeAccess, Set scopes ) { + if (areArgumentsInvalid(resourceId, resourceIndex, revokeAccess, scopes)) { + return null; + } final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes); @@ -178,6 +190,9 @@ public ResourceSharing revokeAccess( * @return True if the record was successfully deleted, false otherwise. */ public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) { + if (areArgumentsInvalid(resourceId, resourceIndex)) { + return false; + } final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, resourceIndex, user.getName()); @@ -198,6 +213,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String resourceInd * @return True if all records were successfully deleted, false otherwise. */ public boolean deleteAllResourceSharingRecordsForCurrentUser() { + final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); @@ -308,4 +324,20 @@ private boolean checkSharing(ResourceSharing document, EntityType entityType, St .orElse(false); // Return false if no matching scope is found } + private boolean areArgumentsInvalid(Object... args) { + if (args == null) { + return true; + } + for (Object arg : args) { + if (arg == null) { + return true; + } + // Additional check for String type arguments + if (arg instanceof String && ((String) arg).trim().isEmpty()) { + return true; + } + } + return false; + } + } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index c0f6ea2bd0..839af57f9c 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -1151,7 +1151,6 @@ public boolean deleteAllRecordsForUser(String name) { * @return A set of deserialized documents. */ private Set getResourcesFromIds(Set resourceIds, String resourceIndex, Class clazz) { - Set result = new HashSet<>(); // stashing Context to avoid permission issues in-case resourceIndex is a system index try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { From 8845812df90124f0e4043bf32daa752cc06d8623 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 13 Dec 2024 13:06:04 -0500 Subject: [PATCH 055/122] Updates SampleResource class structure Signed-off-by: Darshit Chanpura --- .../org/opensearch/sample/SampleResource.java | 21 ++++++++++++++++--- .../create/CreateResourceRestAction.java | 4 ++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java index 07441d96b8..c384dc770e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java @@ -12,6 +12,7 @@ package org.opensearch.sample; import java.io.IOException; +import java.util.Map; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; @@ -22,11 +23,15 @@ public class SampleResource implements Resource { private String name; + private String description; + private Map attributes; public SampleResource() {} public SampleResource(StreamInput in) throws IOException { this.name = in.readString(); + this.description = in.readString(); + this.attributes = in.readMap(StreamInput::readString, StreamInput::readString); } @Override @@ -41,12 +46,14 @@ public String getResourceName() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("name", name).endObject(); + return builder.startObject().field("name", name).field("description", description).field("attributes", attributes).endObject(); } @Override - public void writeTo(StreamOutput streamOutput) throws IOException { - streamOutput.writeString(name); + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeString(description); + out.writeMap(attributes, StreamOutput::writeString, StreamOutput::writeString); } @Override @@ -57,4 +64,12 @@ public String getWriteableName() { public void setName(String name) { this.name = name; } + + public void setDescription(String description) { + this.description = description; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java index f56f61d010..f7aa1c76b5 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java @@ -44,8 +44,12 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli } String name = (String) source.get("name"); + String description = source.containsKey("description") ? (String) source.get("description") : null; + Map attributes = source.containsKey("attributes") ? (Map) source.get("attributes") : null; SampleResource resource = new SampleResource(); resource.setName(name); + resource.setDescription(description); + resource.setAttributes(attributes); final CreateResourceRequest createSampleResourceRequest = new CreateResourceRequest(resource); return channel -> client.executeLocally( CreateResourceAction.INSTANCE, From a55ac2263d4359a1ca77cf4d2ac8553fc8a9b710 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 11:49:25 -0500 Subject: [PATCH 056/122] Adds auditlog capability and conform to changes in core Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 17 +++++++++++++---- .../resources/ResourceAccessHandler.java | 2 +- .../resources/ResourceSharingIndexHandler.java | 6 +++++- .../resources/ResourceSharingIndexListener.java | 6 ++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 118b5bc88b..a2cfded4cc 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -726,7 +726,7 @@ public void onIndexModule(IndexModule indexModule) { if (this.indicesToListen.contains(indexModule.getIndex().getName())) { ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance(); - resourceSharingIndexListener.initialize(threadPool, localClient); + resourceSharingIndexListener.initialize(threadPool, localClient, auditLog); indexModule.addIndexOperationListener(resourceSharingIndexListener); log.warn("Security plugin started listening to operations on index {}", indexModule.getIndex().getName()); } @@ -1215,7 +1215,12 @@ public Collection createComponents( } final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; - ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler(resourceSharingIndex, localClient, threadPool); + ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler( + resourceSharingIndex, + localClient, + threadPool, + auditLog + ); resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler); @@ -2150,8 +2155,12 @@ public Collection getSystemIndexDescriptors(Settings sett ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX ); - final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); - return Collections.singletonList(systemIndexDescriptor); + final SystemIndexDescriptor securityIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); + final SystemIndexDescriptor resourceSharingIndexDescriptor = new SystemIndexDescriptor( + ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, + "Resource Sharing index" + ); + return List.of(securityIndexDescriptor, resourceSharingIndexDescriptor); } @Override diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 41b999c009..f4b9a937c1 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -313,7 +313,7 @@ private boolean checkSharing(ResourceSharing document, EntityType entityType, St .filter(sharedWithScope -> sharedWithScope.getScope().equals(scope)) .findFirst() .map(sharedWithScope -> { - SharedWithScope.SharedWithPerScope scopePermissions = sharedWithScope.getSharedWithPerScope(); + SharedWithScope.ScopeRecipients scopePermissions = sharedWithScope.getSharedWithPerScope(); return switch (entityType) { case EntityType.USERS -> scopePermissions.getUsers().contains(identifier); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 839af57f9c..de802ac485 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -66,6 +66,7 @@ import org.opensearch.search.SearchHit; import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.auditlog.AuditLog; import org.opensearch.threadpool.ThreadPool; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; @@ -84,10 +85,13 @@ public class ResourceSharingIndexHandler { private final ThreadPool threadPool; - public ResourceSharingIndexHandler(final String indexName, final Client client, ThreadPool threadPool) { + private final AuditLog auditLog; + + public ResourceSharingIndexHandler(final String indexName, final Client client, final ThreadPool threadPool, final AuditLog auditLog) { this.resourceSharingIndex = indexName; this.client = client; this.threadPool = threadPool; + this.auditLog = auditLog; } public final static Map INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 2fad56fc1b..6be230f752 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -19,6 +19,7 @@ import org.opensearch.core.index.shard.ShardId; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexingOperationListener; +import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; @@ -53,7 +54,7 @@ public static ResourceSharingIndexListener getInstance() { * @param threadPool The ThreadPool instance to be used for executing operations. * @param client The Client instance to be used for interacting with OpenSearch. */ - public void initialize(ThreadPool threadPool, Client client) { + public void initialize(ThreadPool threadPool, Client client, AuditLog auditLog) { if (initialized) { return; @@ -64,7 +65,8 @@ public void initialize(ThreadPool threadPool, Client client) { this.resourceSharingIndexHandler = new ResourceSharingIndexHandler( ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, client, - threadPool + threadPool, + auditLog ); } From 6269d940dd817be2a4a55212e2ef38c5849af235 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 12:02:32 -0500 Subject: [PATCH 057/122] Adds new scope named public Signed-off-by: Darshit Chanpura --- .../main/java/org/opensearch/sample/SampleResourceScope.java | 4 +++- .../transport/resource/DeleteResourceTransportAction.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java index 90df0d3764..1d6de8c1f7 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java @@ -19,7 +19,9 @@ */ public enum SampleResourceScope implements ResourceAccessScope { - SAMPLE_FULL_ACCESS("sample_full_access"); + SAMPLE_FULL_ACCESS("sample_full_access"), + + PUBLIC("public"); private final String name; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java index bdc19ab8b3..bb403e3704 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java @@ -54,9 +54,9 @@ protected void doExecute(Task task, DeleteResourceRequest request, ActionListene try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { deleteResource(request, ActionListener.wrap(deleteResponse -> { if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) { - listener.onFailure(new ResourceNotFoundException("Resource " + request.getResourceId() + " not found")); + listener.onFailure(new ResourceNotFoundException("Resource " + request.getResourceId() + " not found.")); } else { - listener.onResponse(new DeleteResourceResponse("Resource " + request.getResourceId() + " deleted successfully")); + listener.onResponse(new DeleteResourceResponse("Resource " + request.getResourceId() + " deleted successfully.")); } }, exception -> { log.error("Failed to delete resource: " + request.getResourceId(), exception); From 1c62d367b91d3ab62f1f8a358fe5ed75ab2c9f78 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 12:27:52 -0500 Subject: [PATCH 058/122] Conforms to type bounding change introduced in core Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 3 ++- .../security/resources/ResourceAccessHandler.java | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index a2cfded4cc..544141b8bb 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -70,6 +70,7 @@ import org.opensearch.SpecialPermission; import org.opensearch.Version; import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; @@ -2217,7 +2218,7 @@ private void tryAddSecurityProvider() { } @Override - public Set getAccessibleResourcesForCurrentUser(String systemIndexName, Class clazz) { + public Set getAccessibleResourcesForCurrentUser(String systemIndexName, Class clazz) { return this.resourceAccessHandler.getAccessibleResourcesForCurrentUser(systemIndexName, clazz); } diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index f4b9a937c1..782e4b040b 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -20,6 +20,7 @@ import org.apache.logging.log4j.Logger; import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.accesscontrol.resources.SharedWithScope; @@ -58,7 +59,7 @@ public ResourceAccessHandler( * @param resourceIndex The resource index to check for accessible resources. * @return A set of accessible resource IDs. */ - public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { + public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { if (areArgumentsInvalid(resourceIndex, clazz)) { return Collections.emptySet(); } @@ -226,7 +227,7 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() { * @param resourceIndex The resource index to load resources from. * @return A set of resource IDs. */ - private Set loadAllResources(String resourceIndex, Class clazz) { + private Set loadAllResources(String resourceIndex, Class clazz) { return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, clazz); } @@ -237,7 +238,7 @@ private Set loadAllResources(String resourceIndex, Class clazz) { * @param userName The username of the owner. * @return A set of resource IDs owned by the user. */ - private Set loadOwnResources(String resourceIndex, String userName, Class clazz) { + private Set loadOwnResources(String resourceIndex, String userName, Class clazz) { return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, clazz); } @@ -249,7 +250,12 @@ private Set loadOwnResources(String resourceIndex, String userName, Class * @param entityType The type of entity (e.g., users, roles, backend_roles). * @return A set of resource IDs shared with the specified entities. */ - private Set loadSharedWithResources(String resourceIndex, Set entities, String entityType, Class clazz) { + private Set loadSharedWithResources( + String resourceIndex, + Set entities, + String entityType, + Class clazz + ) { return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, entityType, clazz); } From d8969e57d4ac9abfd7fb0c90553d702663b554ab Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 12:44:05 -0500 Subject: [PATCH 059/122] Updates Resource type Signed-off-by: Darshit Chanpura --- .../java/org/opensearch/sample/Resource.java | 21 ------------------- .../org/opensearch/sample/SampleResource.java | 18 ++++++---------- .../create/CreateResourceRequest.java | 2 +- .../CreateResourceTransportAction.java | 2 +- 4 files changed, 8 insertions(+), 35 deletions(-) delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java deleted file mode 100644 index 4ddb56f395..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/Resource.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.sample; - -import org.opensearch.core.common.io.stream.NamedWriteable; -import org.opensearch.core.xcontent.ToXContentFragment; - -public interface Resource extends NamedWriteable, ToXContentFragment { - String getResourceIndex(); - - String getResourceName(); -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java index c384dc770e..abef02ff35 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java @@ -14,12 +14,11 @@ import java.io.IOException; import java.util.Map; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.XContentBuilder; -import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; - public class SampleResource implements Resource { private String name; @@ -34,16 +33,6 @@ public SampleResource(StreamInput in) throws IOException { this.attributes = in.readMap(StreamInput::readString, StreamInput::readString); } - @Override - public String getResourceIndex() { - return RESOURCE_INDEX_NAME; - } - - @Override - public String getResourceName() { - return this.name; - } - @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.startObject().field("name", name).field("description", description).field("attributes", attributes).endObject(); @@ -72,4 +61,9 @@ public void setDescription(String description) { public void setAttributes(Map attributes) { this.attributes = attributes; } + + @Override + public String getResourceName() { + return name; + } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java index 3f330d9719..abad5cd1c3 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java @@ -10,11 +10,11 @@ import java.io.IOException; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.Resource; /** * Request object for CreateSampleResource transport action diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java index 9a764b61de..052783a90b 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; @@ -23,7 +24,6 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.sample.Resource; import org.opensearch.sample.actions.resource.create.CreateResourceAction; import org.opensearch.sample.actions.resource.create.CreateResourceRequest; import org.opensearch.sample.actions.resource.create.CreateResourceResponse; From c24323c1d39d2fa7bfe573350c584d016d71aaaa Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 15:37:22 -0500 Subject: [PATCH 060/122] Stashes context while fetching resource sharing record Signed-off-by: Darshit Chanpura --- .../ResourceSharingIndexHandler.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index de802ac485..755793c698 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -118,6 +118,7 @@ public ResourceSharingIndexHandler(final String indexName, final Client client, public void createResourceSharingIndexIfAbsent(Callable callable) { // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { + CreateIndexRequest cir = new CreateIndexRequest(resourceSharingIndex).settings(INDEX_SETTINGS).waitForActiveShards(1); ActionListener cirListener = ActionListener.wrap(response -> { LOGGER.info("Resource sharing index {} created.", resourceSharingIndex); @@ -158,7 +159,8 @@ public void createResourceSharingIndexIfAbsent(Callable callable) { */ public ResourceSharing indexResourceSharing(String resourceId, String resourceIndex, CreatedBy createdBy, ShareWith shareWith) throws IOException { - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { ResourceSharing entry = new ResourceSharing(resourceIndex, resourceId, createdBy, shareWith); IndexRequest ir = client.prepareIndex(resourceSharingIndex) @@ -224,7 +226,8 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn public Set fetchAllDocuments(String pluginIndex, Class clazz) { LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); @@ -414,7 +417,8 @@ public Set fetchDocumentsForAGivenScope( Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); searchRequest.scroll(scroll); @@ -521,7 +525,8 @@ public Set fetchDocumentsByField(String pluginIndex, String field, String Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); searchRequest.scroll(scroll); @@ -602,7 +607,8 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) LOGGER.debug("Fetching document from index: {}, with resourceId: {}", pluginIndex, resourceId); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() @@ -838,7 +844,8 @@ public ResourceSharing updateResourceSharingInfo( * */ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId, Script updateScript) { - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { BoolQueryBuilder query = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)); @@ -941,7 +948,8 @@ public ResourceSharing revokeAccess( LOGGER.debug("Revoking access for resource {} in {} for entities: {} and scopes: {}", resourceId, sourceIdx, revokeAccess, scopes); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { Map revoke = new HashMap<>(); for (Map.Entry> entry : revokeAccess.entrySet()) { revoke.put(entry.getKey().name().toLowerCase(), new ArrayList<>(entry.getValue())); @@ -1036,7 +1044,8 @@ public ResourceSharing revokeAccess( public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) { LOGGER.debug("Deleting documents from {} where source_idx = {} and resource_id = {}", resourceSharingIndex, sourceIdx, resourceId); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { DeleteByQueryRequest dbq = new DeleteByQueryRequest(resourceSharingIndex).setQuery( QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) @@ -1124,7 +1133,8 @@ public boolean deleteAllRecordsForUser(String name) { LOGGER.debug("Deleting all records for user {}", name); - try { + // TODO: Once stashContext is replaced with switchContext this call will have to be modified + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(resourceSharingIndex).setQuery( QueryBuilders.termQuery("created_by.user", name) ).setRefresh(true); @@ -1157,6 +1167,7 @@ public boolean deleteAllRecordsForUser(String name) { private Set getResourcesFromIds(Set resourceIds, String resourceIndex, Class clazz) { Set result = new HashSet<>(); // stashing Context to avoid permission issues in-case resourceIndex is a system index + // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { MultiGetRequest request = new MultiGetRequest(); for (String id : resourceIds) { From f514859d757a606dd1627c22a7ff4ea4ad7dca7e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 15:37:51 -0500 Subject: [PATCH 061/122] Fixes accessDeclaredMembers error caused in AuditConfig class Signed-off-by: Darshit Chanpura --- .../security/DefaultObjectMapper.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opensearch/security/DefaultObjectMapper.java b/src/main/java/org/opensearch/security/DefaultObjectMapper.java index 68a537c669..05ceabb86c 100644 --- a/src/main/java/org/opensearch/security/DefaultObjectMapper.java +++ b/src/main/java/org/opensearch/security/DefaultObjectMapper.java @@ -287,12 +287,26 @@ public static TypeFactory getTypeFactory() { return objectMapper.getTypeFactory(); } + @SuppressWarnings("removal") public static Set getFields(Class cls) { - return objectMapper.getSerializationConfig() - .introspect(getTypeFactory().constructType(cls)) - .findProperties() - .stream() - .map(BeanPropertyDefinition::getName) - .collect(ImmutableSet.toImmutableSet()); + final SecurityManager sm = System.getSecurityManager(); + + if (sm != null) { + sm.checkPermission(new SpecialPermission()); + } + + try { + return AccessController.doPrivileged( + (PrivilegedExceptionAction>) () -> objectMapper.getSerializationConfig() + .introspect(getTypeFactory().constructType(cls)) + .findProperties() + .stream() + .map(BeanPropertyDefinition::getName) + .collect(ImmutableSet.toImmutableSet()) + ); + } catch (final PrivilegedActionException e) { + throw (RuntimeException) e.getCause(); + } + } } From 193e846758cbf340f56aa94a84337d19a0a1f0f0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 17:44:50 -0500 Subject: [PATCH 062/122] Changes log levels and improves log statements Signed-off-by: Darshit Chanpura --- .../org/opensearch/security/OpenSearchSecurityPlugin.java | 3 +-- .../security/resources/ResourceSharingIndexListener.java | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 3a31718de4..74025ea4b9 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2128,7 +2128,7 @@ public void onNodeStarted(DiscoveryNode localNode) { String resourceIndex = resourcePlugin.getResourceIndex(); this.indicesToListen.add(resourceIndex); - log.info("Preparing to listen to index: {} of plugin: {}", resourceIndex, resourcePlugin); + log.warn("Security plugin started listening to index: {} of plugin: {}", resourceIndex, resourcePlugin); } final Set securityModules = ReflectionHelper.getModulesLoaded(); @@ -2148,7 +2148,6 @@ public Collection> getGuiceServiceClasses() final List> services = new ArrayList<>(1); services.add(GuiceHolder.class); - log.info("Guice service classes loaded"); return services; } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 6be230f752..1c6950b9ae 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -42,9 +42,7 @@ public class ResourceSharingIndexListener implements IndexingOperationListener { private ResourceSharingIndexListener() {} public static ResourceSharingIndexListener getInstance() { - return ResourceSharingIndexListener.INSTANCE; - } /** @@ -122,11 +120,9 @@ public void postDelete(ShardId shardId, Engine.Delete delete, Engine.DeleteResul boolean success = this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex); if (success) { - log.info("Successfully deleted resource sharing entries for resource {}", resourceId); + log.info("Successfully deleted resource sharing entry for resource {}", resourceId); } else { log.info("Failed to delete resource sharing entry for resource {}", resourceId); } - } - } From 13fdb81445b716f39387e81ae32c462297e434d0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 20:48:37 -0500 Subject: [PATCH 063/122] Bring user notion to security plugin Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 9 +-- .../security/resources/Creator.java | 15 ++++ .../security/resources/Recipient.java | 17 +++++ .../resources/ResourceAccessHandler.java | 57 +++++++++------ .../ResourceSharingIndexHandler.java | 73 +++++++++---------- .../ResourceSharingIndexListener.java | 2 +- ...ourceSharingIndexManagementRepository.java | 1 - 7 files changed, 108 insertions(+), 66 deletions(-) create mode 100644 src/main/java/org/opensearch/security/resources/Creator.java create mode 100644 src/main/java/org/opensearch/security/resources/Recipient.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 74025ea4b9..4153d9749d 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -69,11 +69,7 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; import org.opensearch.Version; -import org.opensearch.accesscontrol.resources.EntityType; -import org.opensearch.accesscontrol.resources.Resource; -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.*; import org.opensearch.action.ActionRequest; import org.opensearch.action.search.PitService; import org.opensearch.action.search.SearchScrollAction; @@ -1230,6 +1226,7 @@ public Collection createComponents( auditLog ); resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); + resourceAccessHandler.initializeRecipientTypes(); rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler); @@ -2255,7 +2252,7 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar public ResourceSharing revokeAccess( String resourceId, String systemIndexName, - Map> entities, + Map> entities, Set scopes ) { return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities, scopes); diff --git a/src/main/java/org/opensearch/security/resources/Creator.java b/src/main/java/org/opensearch/security/resources/Creator.java new file mode 100644 index 0000000000..84a00756c1 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/Creator.java @@ -0,0 +1,15 @@ +package org.opensearch.security.resources; + +public enum Creator { + USER("user"); + + private final String name; + + Creator(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/opensearch/security/resources/Recipient.java b/src/main/java/org/opensearch/security/resources/Recipient.java new file mode 100644 index 0000000000..7cd2ed76ad --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/Recipient.java @@ -0,0 +1,17 @@ +package org.opensearch.security.resources; + +public enum Recipient { + USERS("users"), + ROLES("roles"), + BACKEND_ROLES("backend_roles"); + + private final String name; + + Recipient(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 782e4b040b..eb9a81408d 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -19,7 +19,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.RecipientType; +import org.opensearch.accesscontrol.resources.RecipientTypeRegistry; import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; @@ -53,6 +54,19 @@ public ResourceAccessHandler( this.adminDNs = adminDns; } + /** + * Initializes the recipient types for users, roles, and backend roles. + * These recipient types are used to identify the types of recipients for resource sharing. + */ + public void initializeRecipientTypes() { + RecipientTypeRegistry.registerRecipientType(Recipient.USERS.getName(), new RecipientType(Recipient.USERS.getName())); + RecipientTypeRegistry.registerRecipientType(Recipient.ROLES.getName(), new RecipientType(Recipient.ROLES.getName())); + RecipientTypeRegistry.registerRecipientType( + Recipient.BACKEND_ROLES.getName(), + new RecipientType(Recipient.BACKEND_ROLES.getName()) + ); + } + /** * Returns a set of accessible resources for the current user within the specified resource index. * @@ -82,15 +96,15 @@ public Set getAccessibleResourcesForCurrentUser(String r result.addAll(loadOwnResources(resourceIndex, user.getName(), clazz)); // 1. By username - result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), EntityType.USERS.toString(), clazz)); + result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString(), clazz)); // 2. By roles Set roles = user.getSecurityRoles(); - result.addAll(loadSharedWithResources(resourceIndex, roles, EntityType.ROLES.toString(), clazz)); + result.addAll(loadSharedWithResources(resourceIndex, roles, Recipient.ROLES.toString(), clazz)); // 3. By backend_roles Set backendRoles = user.getRoles(); - result.addAll(loadSharedWithResources(resourceIndex, backendRoles, EntityType.BACKEND_ROLES.toString(), clazz)); + result.addAll(loadSharedWithResources(resourceIndex, backendRoles, Recipient.BACKEND_ROLES.toString(), clazz)); return result; } @@ -127,9 +141,9 @@ public boolean hasPermission(String resourceId, String resourceIndex, String sco if (isSharedWithEveryone(document) || isOwnerOfResource(document, user.getName()) - || isSharedWithEntity(document, EntityType.USERS, Set.of(user.getName()), scope) - || isSharedWithEntity(document, EntityType.ROLES, userRoles, scope) - || isSharedWithEntity(document, EntityType.BACKEND_ROLES, userBackendRoles, scope)) { + || isSharedWithEntity(document, Recipient.USERS, Set.of(user.getName()), scope) + || isSharedWithEntity(document, Recipient.ROLES, userRoles, scope) + || isSharedWithEntity(document, Recipient.BACKEND_ROLES, userBackendRoles, scope)) { LOGGER.info("User {} has {} access to {}", user.getName(), scope, resourceId); return true; } @@ -169,7 +183,7 @@ public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareW public ResourceSharing revokeAccess( String resourceId, String resourceIndex, - Map> revokeAccess, + Map> revokeAccess, Set scopes ) { if (areArgumentsInvalid(resourceId, resourceIndex, revokeAccess, scopes)) { @@ -247,16 +261,16 @@ private Set loadOwnResources(String resourceIndex, Strin * * @param resourceIndex The resource index to load resources from. * @param entities The set of entities to check for shared resources. - * @param entityType The type of entity (e.g., users, roles, backend_roles). + * @param RecipientType The type of entity (e.g., users, roles, backend_roles). * @return A set of resource IDs shared with the specified entities. */ private Set loadSharedWithResources( String resourceIndex, Set entities, - String entityType, + String RecipientType, Class clazz ) { - return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, entityType, clazz); + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, RecipientType, clazz); } /** @@ -267,21 +281,21 @@ private Set loadSharedWithResources( * @return True if the resource is owned by the user, false otherwise. */ private boolean isOwnerOfResource(ResourceSharing document, String userName) { - return document.getCreatedBy() != null && document.getCreatedBy().getUser().equals(userName); + return document.getCreatedBy() != null && document.getCreatedBy().getCreator().equals(userName); } /** * Checks if the given resource is shared with the specified entities and scope. * * @param document The ResourceSharing document to check. - * @param entityType The type of entity (e.g., users, roles, backend_roles). + * @param recipient The recipient entity * @param entities The set of entities to check for sharing. * @param scope The permission scope to check. * @return True if the resource is shared with the entities and scope, false otherwise. */ - private boolean isSharedWithEntity(ResourceSharing document, EntityType entityType, Set entities, String scope) { + private boolean isSharedWithEntity(ResourceSharing document, Recipient recipient, Set entities, String scope) { for (String entity : entities) { - if (checkSharing(document, entityType, entity, scope)) { + if (checkSharing(document, recipient, entity, scope)) { return true; } } @@ -303,12 +317,12 @@ private boolean isSharedWithEveryone(ResourceSharing document) { * Checks if the given resource is shared with the specified entity and scope. * * @param document The ResourceSharing document to check. - * @param entityType The type of entity (e.g., users, roles, backend_roles). + * @param recipient The recipient entity * @param identifier The identifier of the entity to check for sharing. * @param scope The permission scope to check. * @return True if the resource is shared with the entity and scope, false otherwise. */ - private boolean checkSharing(ResourceSharing document, EntityType entityType, String identifier, String scope) { + private boolean checkSharing(ResourceSharing document, Recipient recipient, String identifier, String scope) { if (document.getShareWith() == null) { return false; } @@ -320,11 +334,12 @@ private boolean checkSharing(ResourceSharing document, EntityType entityType, St .findFirst() .map(sharedWithScope -> { SharedWithScope.ScopeRecipients scopePermissions = sharedWithScope.getSharedWithPerScope(); + Map> recipients = scopePermissions.getRecipients(); - return switch (entityType) { - case EntityType.USERS -> scopePermissions.getUsers().contains(identifier); - case EntityType.ROLES -> scopePermissions.getRoles().contains(identifier); - case EntityType.BACKEND_ROLES -> scopePermissions.getBackendRoles().contains(identifier); + return switch (recipient) { + case Recipient.USERS, Recipient.ROLES, Recipient.BACKEND_ROLES -> recipients.get( + RecipientTypeRegistry.fromValue(recipient.getName()) + ).contains(identifier); }; }) .orElse(false); // Return false if no matching scope is found diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 755793c698..83341b1ff2 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -25,7 +25,7 @@ import org.apache.logging.log4j.Logger; import org.opensearch.accesscontrol.resources.CreatedBy; -import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.RecipientType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.action.admin.indices.create.CreateIndexRequest; @@ -266,7 +266,7 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { * *

    The method executes the following steps: *

      - *
    1. Validates the entityType parameter
    2. + *
    3. Validates the RecipientType parameter
    4. *
    5. Creates a scrolling search request with a compound query
    6. *
    7. Processes results in batches using scroll API
    8. *
    9. Collects all matching resource IDs
    10. @@ -285,9 +285,9 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { * "should": [ * { * "nested": { - * "path": "share_with.*.entityType", + * "path": "share_with.*.RecipientType", * "query": { - * "term": { "share_with.*.entityType": "entity_value" } + * "term": { "share_with.*.RecipientType": "entity_value" } * } * } * } @@ -304,8 +304,8 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { * * * @param pluginIndex The source index to match against the source_idx field - * @param entities Set of values to match in the specified entityType field - * @param entityType The type of association with the resource. Must be one of: + * @param entities Set of values to match in the specified RecipientType field + * @param RecipientType The type of association with the resource. Must be one of: *
        *
      • "users" - for user-based access
      • *
      • "roles" - for role-based access
      • @@ -327,9 +327,9 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { *
      */ - public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String entityType, Class clazz) { + public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String RecipientType, Class clazz) { // "*" must match all scopes - return fetchDocumentsForAGivenScope(pluginIndex, entities, entityType, "*", clazz); + return fetchDocumentsForAGivenScope(pluginIndex, entities, RecipientType, "*", clazz); } /** @@ -338,7 +338,7 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent * *

      The method executes the following steps: *

        - *
      1. Validates the entityType parameter
      2. + *
      3. Validates the RecipientType parameter
      4. *
      5. Creates a scrolling search request with a compound query
      6. *
      7. Processes results in batches using scroll API
      8. *
      9. Collects all matching resource IDs
      10. @@ -357,9 +357,9 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent * "should": [ * { * "nested": { - * "path": "share_with.scope.entityType", + * "path": "share_with.scope.RecipientType", * "query": { - * "term": { "share_with.scope.entityType": "entity_value" } + * "term": { "share_with.scope.RecipientType": "entity_value" } * } * } * } @@ -376,8 +376,8 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent * * * @param pluginIndex The source index to match against the source_idx field - * @param entities Set of values to match in the specified entityType field - * @param entityType The type of association with the resource. Must be one of: + * @param entities Set of values to match in the specified RecipientType field + * @param RecipientType The type of association with the resource. Must be one of: *
          *
        • "users" - for user-based access
        • *
        • "roles" - for role-based access
        • @@ -402,7 +402,7 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent public Set fetchDocumentsForAGivenScope( String pluginIndex, Set entities, - String entityType, + String RecipientType, String scope, Class clazz ) { @@ -410,7 +410,7 @@ public Set fetchDocumentsForAGivenScope( "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", pluginIndex, scope, - entityType, + RecipientType, entities ); @@ -428,13 +428,13 @@ public Set fetchDocumentsForAGivenScope( if ("*".equals(scope)) { for (String entity : entities) { shouldQuery.should( - QueryBuilders.multiMatchQuery(entity, "share_with.*." + entityType + ".keyword") + QueryBuilders.multiMatchQuery(entity, "share_with.*." + RecipientType + ".keyword") .type(MultiMatchQueryBuilder.Type.BEST_FIELDS) ); } } else { for (String entity : entities) { - shouldQuery.should(QueryBuilders.termQuery("share_with." + scope + "." + entityType + ".keyword", entity)); + shouldQuery.should(QueryBuilders.termQuery("share_with." + scope + "." + RecipientType + ".keyword", entity)); } } shouldQuery.minimumShouldMatch(1); @@ -449,11 +449,11 @@ public Set fetchDocumentsForAGivenScope( } catch (Exception e) { LOGGER.error( - "Failed to fetch documents from {} for criteria - pluginIndex: {}, scope: {}, entityType: {}, entities: {}", + "Failed to fetch documents from {} for criteria - pluginIndex: {}, scope: {}, RecipientType: {}, entities: {}", resourceSharingIndex, pluginIndex, scope, - entityType, + RecipientType, entities, e ); @@ -618,7 +618,6 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery).size(1); // We only need one document since // a resource must have only one // sharing entry - searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = client.search(searchRequest).actionGet(); @@ -733,14 +732,14 @@ public ResourceSharing updateResourceSharingInfo( // Check if the user requesting to share is the owner of the resource // TODO Add a way for users who are not creators to be able to share the resource ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); - if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { + if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); return null; } CreatedBy createdBy; if (currentSharingInfo == null) { - createdBy = new CreatedBy(requestUserName); + createdBy = new CreatedBy(Creator.USER.getName(), requestUserName); } else { createdBy = currentSharingInfo.getCreatedBy(); } @@ -914,23 +913,23 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * @throws IllegalArgumentException if resourceId, sourceIdx is null/empty, or if revokeAccess is null/empty * @throws RuntimeException if the update operation fails or encounters an error * - * @see EntityType + * @see RecipientType * @see ResourceSharing * * @apiNote This method modifies the existing document. If no modifications are needed (i.e., specified * entities don't exist in the current configuration), the original document is returned unchanged. * @example *
          -     * Map> revokeAccess = new HashMap<>();
          -     * revokeAccess.put(EntityType.USER, Set.of("user1", "user2"));
          -     * revokeAccess.put(EntityType.ROLE, Set.of("role1"));
          +     * Map> revokeAccess = new HashMap<>();
          +     * revokeAccess.put(RecipientType.USER, Set.of("user1", "user2"));
          +     * revokeAccess.put(RecipientType.ROLE, Set.of("role1"));
                * ResourceSharing updated = revokeAccess("resourceId", "pluginIndex", revokeAccess);
                * 
          */ public ResourceSharing revokeAccess( String resourceId, String sourceIdx, - Map> revokeAccess, + Map> revokeAccess, Set scopes, String requestUserName, boolean isAdmin @@ -941,7 +940,7 @@ public ResourceSharing revokeAccess( // TODO Check if access can be revoked by non-creator ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); - if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getUser().equals(requestUserName)) { + if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { LOGGER.error("User {} is not authorized to revoke access to resource {}", requestUserName, resourceId); return null; } @@ -951,8 +950,8 @@ public ResourceSharing revokeAccess( // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { Map revoke = new HashMap<>(); - for (Map.Entry> entry : revokeAccess.entrySet()) { - revoke.put(entry.getKey().name().toLowerCase(), new ArrayList<>(entry.getValue())); + for (Map.Entry> entry : revokeAccess.entrySet()) { + revoke.put(entry.getKey().getType().toLowerCase(), new ArrayList<>(entry.getValue())); } List scopesToUse = scopes != null ? new ArrayList<>(scopes) : new ArrayList<>(); @@ -966,18 +965,18 @@ public ResourceSharing revokeAccess( def existingScope = ctx._source.share_with.get(scopeName); for (def entry : params.revokeAccess.entrySet()) { - def entityType = entry.getKey(); + def RecipientType = entry.getKey(); def entitiesToRemove = entry.getValue(); - if (existingScope.containsKey(entityType) && existingScope[entityType] != null) { - if (!(existingScope[entityType] instanceof HashSet)) { - existingScope[entityType] = new HashSet(existingScope[entityType]); + if (existingScope.containsKey(RecipientType) && existingScope[RecipientType] != null) { + if (!(existingScope[RecipientType] instanceof HashSet)) { + existingScope[RecipientType] = new HashSet(existingScope[RecipientType]); } - existingScope[entityType].removeAll(entitiesToRemove); + existingScope[RecipientType].removeAll(entitiesToRemove); - if (existingScope[entityType].isEmpty()) { - existingScope.remove(entityType); + if (existingScope[RecipientType].isEmpty()) { + existingScope.remove(RecipientType); } } } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 1c6950b9ae..58fe4cccf4 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -94,7 +94,7 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re ResourceSharing sharing = this.resourceSharingIndexHandler.indexResourceSharing( resourceId, resourceIndex, - new CreatedBy(user.getName()), + new CreatedBy(Creator.USER.getName(), user.getName()), null ); log.info("Successfully created a resource sharing entry {}", sharing); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java index 60cb48145f..17f57269be 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java @@ -35,5 +35,4 @@ public void createResourceSharingIndexIfAbsent() { this.resourceSharingIndexHandler.createResourceSharingIndexIfAbsent(() -> null); } - } From 413bb0b92ab353dcb4ac9fc5e244ad268dab4e43 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 21:21:19 -0500 Subject: [PATCH 064/122] Conforms to changes in core Signed-off-by: Darshit Chanpura --- .../revoke/RevokeResourceAccessRequest.java | 12 ++++++------ .../revoke/RevokeResourceAccessRestAction.java | 15 +++++---------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java index e97a2d1244..f7b4e7b5d7 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java @@ -12,7 +12,7 @@ import java.util.Map; import java.util.Set; -import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.RecipientType; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; @@ -22,10 +22,10 @@ public class RevokeResourceAccessRequest extends ActionRequest { private final String resourceId; - private final Map> revokeAccess; + private final Map> revokeAccess; private final Set scopes; - public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess, Set scopes) { + public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess, Set scopes) { this.resourceId = resourceId; this.revokeAccess = revokeAccess; this.scopes = scopes; @@ -33,7 +33,7 @@ public RevokeResourceAccessRequest(String resourceId, Map EntityType.valueOf(input.readString()), input -> input.readSet(StreamInput::readString)); + this.revokeAccess = in.readMap(input -> new RecipientType(input.readString()), input -> input.readSet(StreamInput::readString)); this.scopes = in.readSet(StreamInput::readString); } @@ -42,7 +42,7 @@ public void writeTo(final StreamOutput out) throws IOException { out.writeString(resourceId); out.writeMap( revokeAccess, - (streamOutput, entityType) -> streamOutput.writeString(entityType.name()), + (streamOutput, recipientType) -> streamOutput.writeString(recipientType.getType()), StreamOutput::writeStringCollection ); out.writeStringCollection(scopes); @@ -62,7 +62,7 @@ public String getResourceId() { return resourceId; } - public Map> getRevokeAccess() { + public Map> getRevokeAccess() { return revokeAccess; } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java index 1145457863..387d02502f 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java @@ -12,7 +12,8 @@ import java.util.*; import java.util.stream.Collectors; -import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.RecipientType; +import org.opensearch.accesscontrol.resources.RecipientTypeRegistry; import org.opensearch.client.node.NodeClient; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.BaseRestHandler; @@ -46,15 +47,9 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli String resourceId = (String) source.get("resource_id"); @SuppressWarnings("unchecked") Map> revokeSource = (Map>) source.get("entities"); - Map> revoke = revokeSource.entrySet().stream().collect(Collectors.toMap(entry -> { - try { - return EntityType.fromValue(entry.getKey()); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException( - "Invalid entity type: " + entry.getKey() + ". Valid values are: " + Arrays.toString(EntityType.values()) - ); - } - }, Map.Entry::getValue)); + Map> revoke = revokeSource.entrySet() + .stream() + .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); @SuppressWarnings("unchecked") Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke, scopes); From 3ab92bb02d203170106a8865e5b1cfcbaea29785 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 7 Jan 2025 15:51:57 -0500 Subject: [PATCH 065/122] Makes changes to conform to SPI model Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 23 ++++++ .../org/opensearch/sample/SampleResource.java | 7 +- .../sample/SampleResourceParser.java | 41 ++++++++++ .../sample/SampleResourcePlugin.java | 35 +++------ .../sample/SampleResourceScope.java | 7 +- .../list/ListAccessibleResourcesAction.java | 29 ------- .../list/ListAccessibleResourcesRequest.java | 39 ---------- .../list/ListAccessibleResourcesResponse.java | 47 ------------ .../ListAccessibleResourcesRestAction.java | 44 ----------- .../revoke/RevokeResourceAccessAction.java | 21 ------ .../revoke/RevokeResourceAccessRequest.java | 72 ------------------ .../revoke/RevokeResourceAccessResponse.java | 42 ----------- .../RevokeResourceAccessRestAction.java | 62 --------------- .../access/share/ShareResourceAction.java | 26 ------- .../access/share/ShareResourceRequest.java | 58 -------------- .../access/share/ShareResourceResponse.java | 52 ------------- .../access/share/ShareResourceRestAction.java | 75 ------------------- .../create/CreateResourceRequest.java | 2 +- ...istAccessibleResourcesTransportAction.java | 55 -------------- .../RevokeResourceAccessTransportAction.java | 62 --------------- .../access/ShareResourceTransportAction.java | 60 --------------- .../VerifyResourceAccessTransportAction.java | 5 +- .../CreateResourceTransportAction.java | 2 +- .../opensearch/sample/utils/Validation.java | 2 +- 24 files changed, 91 insertions(+), 777 deletions(-) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle index e9822c1f22..efdf700599 100644 --- a/sample-resource-plugin/build.gradle +++ b/sample-resource-plugin/build.gradle @@ -9,6 +9,10 @@ apply plugin: 'opensearch.java-rest-test' import org.opensearch.gradle.test.RestIntegTestTask +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} opensearchplugin { name 'opensearch-sample-resource-plugin' @@ -20,6 +24,20 @@ ext { projectSubstitutions = [:] licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') + opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") + isSnapshot = "true" == System.getProperty("build.snapshot", "true") + buildVersionQualifier = System.getProperty("build.version_qualifier", "") + + version_tokens = opensearch_version.tokenize('-') + opensearch_build = version_tokens[0] + '.0' + + + if (buildVersionQualifier) { + opensearch_build += "-${buildVersionQualifier}" + } + if (isSnapshot) { + opensearch_build += "-SNAPSHOT" + } } repositories { @@ -29,8 +47,13 @@ repositories { } dependencies { + implementation "org.opensearch:opensearch-resource-sharing-spi:${opensearch_build}" + implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" } +dependencyLicenses.enabled = false +thirdPartyAudit.enabled = false + def es_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile es_tmp_dir.mkdirs() diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java index abef02ff35..a265f0cdaa 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java @@ -14,10 +14,10 @@ import java.io.IOException; import java.util.Map; -import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.security.spi.resources.Resource; public class SampleResource implements Resource { @@ -66,4 +66,9 @@ public void setAttributes(Map attributes) { public String getResourceName() { return name; } + + @Override + public Resource readFrom(StreamInput streamInput) throws IOException { + return new SampleResource(streamInput); + } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java new file mode 100644 index 0000000000..4bb80fe0e4 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java @@ -0,0 +1,41 @@ +/* + * 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.sample; + +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.opensearch.SpecialPermission; +import org.opensearch.security.spi.resources.ResourceParser; + +@SuppressWarnings("removal") +public class SampleResourceParser implements ResourceParser { + @Override + public SampleResource parse(String s) throws IOException { + ObjectMapper obj = new ObjectMapper(); + final SecurityManager sm = System.getSecurityManager(); + + if (sm != null) { + sm.checkPermission(new SpecialPermission()); + } + + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> obj.readValue(s, SampleResource.class)); + } catch (final PrivilegedActionException e) { + throw (IOException) e.getCause(); + } + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 3119e2203a..4c0ab20ffa 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -17,7 +17,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.action.ActionRequest; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; @@ -39,30 +38,23 @@ import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.ActionPlugin; import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.ResourcePlugin; import org.opensearch.plugins.SystemIndexPlugin; import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; -import org.opensearch.sample.actions.access.list.ListAccessibleResourcesAction; -import org.opensearch.sample.actions.access.list.ListAccessibleResourcesRestAction; -import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessAction; -import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessRestAction; -import org.opensearch.sample.actions.access.share.ShareResourceAction; -import org.opensearch.sample.actions.access.share.ShareResourceRestAction; import org.opensearch.sample.actions.access.verify.VerifyResourceAccessAction; import org.opensearch.sample.actions.access.verify.VerifyResourceAccessRestAction; import org.opensearch.sample.actions.resource.create.CreateResourceAction; import org.opensearch.sample.actions.resource.create.CreateResourceRestAction; import org.opensearch.sample.actions.resource.delete.DeleteResourceAction; import org.opensearch.sample.actions.resource.delete.DeleteResourceRestAction; -import org.opensearch.sample.transport.access.ListAccessibleResourcesTransportAction; -import org.opensearch.sample.transport.access.RevokeResourceAccessTransportAction; -import org.opensearch.sample.transport.access.ShareResourceTransportAction; import org.opensearch.sample.transport.access.VerifyResourceAccessTransportAction; import org.opensearch.sample.transport.resource.CreateResourceTransportAction; import org.opensearch.sample.transport.resource.DeleteResourceTransportAction; import org.opensearch.script.ScriptService; +import org.opensearch.security.spi.resources.ResourceParser; +import org.opensearch.security.spi.resources.ResourceService; +import org.opensearch.security.spi.resources.ResourceSharingExtension; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; @@ -73,7 +65,7 @@ * It uses ".sample_resources" index to manage its resources, and exposes a REST API * */ -public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin { +public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourceSharingExtension { private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class); @Override @@ -104,23 +96,13 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - return List.of( - new CreateResourceRestAction(), - new ListAccessibleResourcesRestAction(), - new VerifyResourceAccessRestAction(), - new RevokeResourceAccessRestAction(), - new ShareResourceRestAction(), - new DeleteResourceRestAction() - ); + return List.of(new CreateResourceRestAction(), new VerifyResourceAccessRestAction(), new DeleteResourceRestAction()); } @Override public List> getActions() { return List.of( new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class), - new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class), - new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class), - new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, RevokeResourceAccessTransportAction.class), new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class), new ActionHandler<>(DeleteResourceAction.INSTANCE, DeleteResourceTransportAction.class) ); @@ -134,7 +116,7 @@ public Collection getSystemIndexDescriptors(Settings sett @Override public String getResourceType() { - return ""; + return SampleResource.class.getCanonicalName(); } @Override @@ -142,6 +124,11 @@ public String getResourceIndex() { return RESOURCE_INDEX_NAME; } + @Override + public ResourceParser getResourceParser() { + return new SampleResourceParser(); + } + @Override public Collection> getGuiceServiceClasses() { final List> services = new ArrayList<>(1); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java index 1d6de8c1f7..cfec368aa7 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceScope.java @@ -11,13 +11,13 @@ package org.opensearch.sample; -import org.opensearch.accesscontrol.resources.ResourceAccessScope; +import org.opensearch.security.spi.resources.ResourceAccessScope; /** * This class demonstrates a sample implementation of Basic Access Scopes to fit each plugin's use-case. * The plugin then uses this scope when seeking access evaluation for a user on a particular resource. */ -public enum SampleResourceScope implements ResourceAccessScope { +public enum SampleResourceScope implements ResourceAccessScope { SAMPLE_FULL_ACCESS("sample_full_access"), @@ -29,7 +29,8 @@ public enum SampleResourceScope implements ResourceAccessScope { this.name = scopeName; } - public String getName() { + @Override + public String value() { return name; } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java deleted file mode 100644 index 3bea515a19..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesAction.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.list; - -import org.opensearch.action.ActionType; - -/** - * Action to list sample resources - */ -public class ListAccessibleResourcesAction extends ActionType { - /** - * List sample resource action instance - */ - public static final ListAccessibleResourcesAction INSTANCE = new ListAccessibleResourcesAction(); - /** - * List sample resource action name - */ - public static final String NAME = "cluster:admin/sample-resource-plugin/list"; - - private ListAccessibleResourcesAction() { - super(NAME, ListAccessibleResourcesResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java deleted file mode 100644 index 4a9315bfd9..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.list; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; - -/** - * Request object for ListSampleResource transport action - */ -public class ListAccessibleResourcesRequest extends ActionRequest { - - public ListAccessibleResourcesRequest() {} - - /** - * Constructor with stream input - * @param in the stream input - * @throws IOException IOException - */ - public ListAccessibleResourcesRequest(final StreamInput in) throws IOException {} - - @Override - public void writeTo(final StreamOutput out) throws IOException {} - - @Override - public ActionRequestValidationException validate() { - return null; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java deleted file mode 100644 index 9c5d2a3e8a..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.list; - -import java.io.IOException; -import java.util.Set; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.sample.SampleResource; - -/** - * Response to a ListAccessibleResourcesRequest - */ -public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { - private final Set resources; - - public ListAccessibleResourcesResponse(Set resources) { - this.resources = resources; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeCollection(resources); - } - - public ListAccessibleResourcesResponse(final StreamInput in) throws IOException { - this.resources = in.readSet(SampleResource::new); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("resources", resources); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java deleted file mode 100644 index c387eacf90..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/list/ListAccessibleResourcesRestAction.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.list; - -import java.util.List; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.GET; - -public class ListAccessibleResourcesRestAction extends BaseRestHandler { - - public ListAccessibleResourcesRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/list")); - } - - @Override - public String getName() { - return "list_sample_resources"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { - final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(); - return channel -> client.executeLocally( - ListAccessibleResourcesAction.INSTANCE, - listAccessibleResourcesRequest, - new RestToXContentListener<>(channel) - ); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java deleted file mode 100644 index a040cb0732..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessAction.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.revoke; - -import org.opensearch.action.ActionType; - -public class RevokeResourceAccessAction extends ActionType { - public static final RevokeResourceAccessAction INSTANCE = new RevokeResourceAccessAction(); - - public static final String NAME = "cluster:admin/sample-resource-plugin/revoke"; - - private RevokeResourceAccessAction() { - super(NAME, RevokeResourceAccessResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java deleted file mode 100644 index f7b4e7b5d7..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRequest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.revoke; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; - -import org.opensearch.accesscontrol.resources.RecipientType; -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.utils.Validation; - -public class RevokeResourceAccessRequest extends ActionRequest { - - private final String resourceId; - private final Map> revokeAccess; - private final Set scopes; - - public RevokeResourceAccessRequest(String resourceId, Map> revokeAccess, Set scopes) { - this.resourceId = resourceId; - this.revokeAccess = revokeAccess; - this.scopes = scopes; - } - - public RevokeResourceAccessRequest(StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.revokeAccess = in.readMap(input -> new RecipientType(input.readString()), input -> input.readSet(StreamInput::readString)); - this.scopes = in.readSet(StreamInput::readString); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeMap( - revokeAccess, - (streamOutput, recipientType) -> streamOutput.writeString(recipientType.getType()), - StreamOutput::writeStringCollection - ); - out.writeStringCollection(scopes); - } - - @Override - public ActionRequestValidationException validate() { - - if (!(this.scopes == null)) { - return Validation.validateScopes(this.scopes); - } - - return null; - } - - public String getResourceId() { - return resourceId; - } - - public Map> getRevokeAccess() { - return revokeAccess; - } - - public Set getScopes() { - return scopes; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java deleted file mode 100644 index 4cfd3d74e5..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.revoke; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class RevokeResourceAccessResponse extends ActionResponse implements ToXContentObject { - private final String message; - - public RevokeResourceAccessResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - public RevokeResourceAccessResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java deleted file mode 100644 index 387d02502f..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/revoke/RevokeResourceAccessRestAction.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.revoke; - -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - -import org.opensearch.accesscontrol.resources.RecipientType; -import org.opensearch.accesscontrol.resources.RecipientTypeRegistry; -import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.POST; - -public class RevokeResourceAccessRestAction extends BaseRestHandler { - - public RevokeResourceAccessRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/revoke")); - } - - @Override - public String getName() { - return "revoke_sample_resources_access"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceId = (String) source.get("resource_id"); - @SuppressWarnings("unchecked") - Map> revokeSource = (Map>) source.get("entities"); - Map> revoke = revokeSource.entrySet() - .stream() - .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); - @SuppressWarnings("unchecked") - Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); - final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest(resourceId, revoke, scopes); - return channel -> client.executeLocally( - RevokeResourceAccessAction.INSTANCE, - revokeResourceAccessRequest, - new RestToXContentListener<>(channel) - ); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java deleted file mode 100644 index 768a811e27..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceAction.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.share; - -import org.opensearch.action.ActionType; - -public class ShareResourceAction extends ActionType { - /** - * List sample resource action instance - */ - public static final ShareResourceAction INSTANCE = new ShareResourceAction(); - /** - * List sample resource action name - */ - public static final String NAME = "cluster:admin/sample-resource-plugin/share"; - - private ShareResourceAction() { - super(NAME, ShareResourceResponse::new); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java deleted file mode 100644 index 6c2ed12e73..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRequest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.share; - -import java.io.IOException; -import java.util.stream.Collectors; - -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.accesscontrol.resources.SharedWithScope; -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.sample.utils.Validation; - -public class ShareResourceRequest extends ActionRequest { - - private final String resourceId; - private final ShareWith shareWith; - - public ShareResourceRequest(String resourceId, ShareWith shareWith) { - this.resourceId = resourceId; - this.shareWith = shareWith; - } - - public ShareResourceRequest(StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.shareWith = in.readNamedWriteable(ShareWith.class); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeNamedWriteable(shareWith); - } - - @Override - public ActionRequestValidationException validate() { - - return Validation.validateScopes( - shareWith.getSharedWithScopes().stream().map(SharedWithScope::getScope).collect(Collectors.toSet()) - ); - } - - public String getResourceId() { - return resourceId; - } - - public ShareWith getShareWith() { - return shareWith; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java deleted file mode 100644 index 035a9a245e..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceResponse.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.share; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class ShareResourceResponse extends ActionResponse implements ToXContentObject { - private final String message; - - /** - * Default constructor - * - * @param message The message - */ - public ShareResourceResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - /** - * Constructor with StreamInput - * - * @param in the stream input - */ - public ShareResourceResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java deleted file mode 100644 index 0db4208c05..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/share/ShareResourceRestAction.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.actions.access.share; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.client.node.NodeClient; -import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.core.xcontent.NamedXContentRegistry; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static java.util.Collections.singletonList; -import static org.opensearch.rest.RestRequest.Method.POST; - -public class ShareResourceRestAction extends BaseRestHandler { - - public ShareResourceRestAction() {} - - @Override - public List routes() { - return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/share")); - } - - @Override - public String getName() { - return "share_sample_resources"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceId = (String) source.get("resource_id"); - - ShareWith shareWith = parseShareWith(source); - final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, shareWith); - return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); - } - - private ShareWith parseShareWith(Map source) throws IOException { - @SuppressWarnings("unchecked") - Map shareWithMap = (Map) source.get("share_with"); - if (shareWithMap == null || shareWithMap.isEmpty()) { - throw new IllegalArgumentException("share_with is required and cannot be empty"); - } - - String jsonString = XContentFactory.jsonBuilder().map(shareWithMap).toString(); - - try ( - XContentParser parser = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, jsonString) - ) { - return ShareWith.fromXContent(parser); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid share_with structure: " + e.getMessage(), e); - } - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java index abad5cd1c3..fe579ff0d1 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java @@ -10,11 +10,11 @@ import java.io.IOException; -import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.action.ActionRequest; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.security.spi.resources.Resource; /** * Request object for CreateSampleResource transport action diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java deleted file mode 100644 index 57c2c7889f..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ListAccessibleResourcesTransportAction.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport.access; - -import java.util.Set; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.sample.SampleResource; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.access.list.ListAccessibleResourcesAction; -import org.opensearch.sample.actions.access.list.ListAccessibleResourcesRequest; -import org.opensearch.sample.actions.access.list.ListAccessibleResourcesResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; - -public class ListAccessibleResourcesTransportAction extends HandledTransportAction< - ListAccessibleResourcesRequest, - ListAccessibleResourcesResponse> { - private static final Logger log = LogManager.getLogger(ListAccessibleResourcesTransportAction.class); - - @Inject - public ListAccessibleResourcesTransportAction(TransportService transportService, ActionFilters actionFilters) { - super(ListAccessibleResourcesAction.NAME, transportService, actionFilters, ListAccessibleResourcesRequest::new); - } - - @Override - protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { - try { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - Set resources = rs.getResourceAccessControlPlugin() - .getAccessibleResourcesForCurrentUser(RESOURCE_INDEX_NAME, SampleResource.class); - log.info("Successfully fetched accessible resources for current user : {}", resources); - listener.onResponse(new ListAccessibleResourcesResponse(resources)); - } catch (Exception e) { - log.info("Failed to list accessible resources for current user: ", e); - listener.onFailure(e); - } - - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java deleted file mode 100644 index 027e1fffe3..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/RevokeResourceAccessTransportAction.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport.access; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessAction; -import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessRequest; -import org.opensearch.sample.actions.access.revoke.RevokeResourceAccessResponse; -import org.opensearch.sample.utils.SampleResourcePluginException; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; - -public class RevokeResourceAccessTransportAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(RevokeResourceAccessTransportAction.class); - - @Inject - public RevokeResourceAccessTransportAction(TransportService transportService, ActionFilters actionFilters) { - super(RevokeResourceAccessAction.NAME, transportService, actionFilters, RevokeResourceAccessRequest::new); - } - - @Override - protected void doExecute(Task task, RevokeResourceAccessRequest request, ActionListener listener) { - try { - ResourceSharing revoke = revokeAccess(request); - if (revoke == null) { - log.error("Failed to revoke access to resource {}", request.getResourceId()); - SampleResourcePluginException se = new SampleResourcePluginException( - "Failed to revoke access to resource " + request.getResourceId() - ); - listener.onFailure(se); - return; - } - log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); - listener.onResponse(new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.")); - } catch (Exception e) { - listener.onFailure(e); - } - } - - private ResourceSharing revokeAccess(RevokeResourceAccessRequest request) { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - return rs.getResourceAccessControlPlugin() - .revokeAccess(request.getResourceId(), RESOURCE_INDEX_NAME, request.getRevokeAccess(), request.getScopes()); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java deleted file mode 100644 index 3288352d0b..0000000000 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/ShareResourceTransportAction.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.sample.transport.access; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.accesscontrol.resources.ResourceService; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.sample.SampleResourcePlugin; -import org.opensearch.sample.actions.access.share.ShareResourceAction; -import org.opensearch.sample.actions.access.share.ShareResourceRequest; -import org.opensearch.sample.actions.access.share.ShareResourceResponse; -import org.opensearch.sample.utils.SampleResourcePluginException; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; - -public class ShareResourceTransportAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); - - @Inject - public ShareResourceTransportAction(TransportService transportService, ActionFilters actionFilters) { - super(ShareResourceAction.NAME, transportService, actionFilters, ShareResourceRequest::new); - } - - @Override - protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { - ResourceSharing sharing = null; - try { - sharing = shareResource(request); - if (sharing == null) { - log.error("Failed to share resource {}", request.getResourceId()); - SampleResourcePluginException se = new SampleResourcePluginException("Failed to share resource " + request.getResourceId()); - listener.onFailure(se); - return; - } - log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); - listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); - } catch (Exception e) { - listener.onFailure(e); - } - } - - private ResourceSharing shareResource(ShareResourceRequest request) throws Exception { - ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); - return rs.getResourceAccessControlPlugin().shareWith(request.getResourceId(), RESOURCE_INDEX_NAME, request.getShareWith()); - } -} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java index 681e4546cc..13954dbe2b 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java @@ -11,16 +11,17 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.client.Client; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.sample.SampleResourcePlugin; +import org.opensearch.sample.SampleResourceScope; import org.opensearch.sample.actions.access.verify.VerifyResourceAccessAction; import org.opensearch.sample.actions.access.verify.VerifyResourceAccessRequest; import org.opensearch.sample.actions.access.verify.VerifyResourceAccessResponse; +import org.opensearch.security.spi.resources.ResourceService; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -39,7 +40,7 @@ protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionL try { ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService(); boolean hasRequestedScopeAccess = rs.getResourceAccessControlPlugin() - .hasPermission(request.getResourceId(), RESOURCE_INDEX_NAME, request.getScope()); + .hasPermission(request.getResourceId(), RESOURCE_INDEX_NAME, SampleResourceScope.valueOf(request.getScope())); StringBuilder sb = new StringBuilder(); sb.append("User "); diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java index 052783a90b..ad82e19576 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java @@ -13,7 +13,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; @@ -27,6 +26,7 @@ import org.opensearch.sample.actions.resource.create.CreateResourceAction; import org.opensearch.sample.actions.resource.create.CreateResourceRequest; import org.opensearch.sample.actions.resource.create.CreateResourceResponse; +import org.opensearch.security.spi.resources.Resource; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java index a057d41eed..fac032402c 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java @@ -11,9 +11,9 @@ import java.util.HashSet; import java.util.Set; -import org.opensearch.accesscontrol.resources.ResourceAccessScope; import org.opensearch.action.ActionRequestValidationException; import org.opensearch.sample.SampleResourceScope; +import org.opensearch.security.spi.resources.ResourceAccessScope; public class Validation { public static ActionRequestValidationException validateScopes(Set scopes) { From 0fe83bad7dba9af04a2fc287c9e84738b8290cca Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 7 Jan 2025 16:56:33 -0500 Subject: [PATCH 066/122] Adds an SPI Model instead of changes in core Signed-off-by: Darshit Chanpura --- build.gradle | 1 + settings.gradle | 3 + spi/README.md | 152 ++++++++++ spi/build.gradle | 74 +++++ .../security/spi/resources/Resource.java | 31 ++ .../ResourceAccessControlPlugin.java | 21 ++ .../spi/resources/ResourceAccessScope.java | 38 +++ .../spi/resources/ResourceParser.java | 21 ++ .../spi/resources/ResourceProvider.java | 33 ++ .../spi/resources/ResourceService.java | 54 ++++ .../resources/ResourceSharingExtension.java | 33 ++ .../DefaultResourceAccessControlPlugin.java | 28 ++ .../spi/resources/fallback/package-info.java | 14 + .../security/spi/resources/package-info.java | 14 + ...faultResourceAccessControlPluginTests.java | 123 ++++++++ .../spi/resources/ResourceServiceTests.java | 220 ++++++++++++++ .../security/OpenSearchSecurityPlugin.java | 70 ++--- .../security/resources/CreatedBy.java | 89 ++++++ .../security/resources/Creator.java | 8 + .../security/resources/Recipient.java | 8 + .../security/resources/RecipientType.java | 32 ++ .../resources/RecipientTypeRegistry.java | 33 ++ .../resources/ResourceAccessHandler.java | 80 +++-- .../security/resources/ResourceSharing.java | 207 +++++++++++++ .../ResourceSharingIndexHandler.java | 59 ++-- .../ResourceSharingIndexListener.java | 4 +- .../security/resources/ShareWith.java | 104 +++++++ .../security/resources/SharedWithScope.java | 169 +++++++++++ .../list/ListAccessibleResourcesAction.java | 25 ++ .../list/ListAccessibleResourcesRequest.java | 51 ++++ .../list/ListAccessibleResourcesResponse.java | 49 +++ .../RestListAccessibleResourcesAction.java | 56 ++++ .../RestRevokeResourceAccessAction.java | 74 +++++ .../revoke/RevokeResourceAccessAction.java | 21 ++ .../revoke/RevokeResourceAccessRequest.java | 79 +++++ .../revoke/RevokeResourceAccessResponse.java | 42 +++ .../access/share/RestShareResourceAction.java | 79 +++++ .../access/share/ShareResourceAction.java | 25 ++ .../access/share/ShareResourceRequest.java | 61 ++++ .../access/share/ShareResourceResponse.java | 42 +++ .../RestVerifyResourceAccessAction.java | 59 ++++ .../verify/VerifyResourceAccessAction.java | 25 ++ .../verify/VerifyResourceAccessRequest.java | 69 +++++ .../verify/VerifyResourceAccessResponse.java | 52 ++++ ...istAccessibleResourcesTransportAction.java | 56 ++++ .../RevokeResourceAccessTransportAction.java | 65 ++++ .../access/ShareResourceTransportAction.java | 61 ++++ .../VerifyResourceAccessTransportAction.java | 66 ++++ .../security/util/ResourceValidation.java | 34 +++ .../security/resources/CreatedByTests.java | 286 ++++++++++++++++++ .../resources/RecipientTypeRegistryTests.java | 33 ++ .../security/resources/ShareWithTests.java | 263 ++++++++++++++++ .../transport/SecurityInterceptorTests.java | 4 +- 53 files changed, 3285 insertions(+), 115 deletions(-) create mode 100644 spi/README.md create mode 100644 spi/build.gradle create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/Resource.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceProvider.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/package-info.java create mode 100644 spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java create mode 100644 spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java create mode 100644 src/main/java/org/opensearch/security/resources/CreatedBy.java create mode 100644 src/main/java/org/opensearch/security/resources/RecipientType.java create mode 100644 src/main/java/org/opensearch/security/resources/RecipientTypeRegistry.java create mode 100644 src/main/java/org/opensearch/security/resources/ResourceSharing.java create mode 100644 src/main/java/org/opensearch/security/resources/ShareWith.java create mode 100644 src/main/java/org/opensearch/security/resources/SharedWithScope.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java create mode 100644 src/main/java/org/opensearch/security/transport/resources/access/ListAccessibleResourcesTransportAction.java create mode 100644 src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java create mode 100644 src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java create mode 100644 src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java create mode 100644 src/main/java/org/opensearch/security/util/ResourceValidation.java create mode 100644 src/test/java/org/opensearch/security/resources/CreatedByTests.java create mode 100644 src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java create mode 100644 src/test/java/org/opensearch/security/resources/ShareWithTests.java diff --git a/build.gradle b/build.gradle index cacfec77c5..2124b0d9de 100644 --- a/build.gradle +++ b/build.gradle @@ -574,6 +574,7 @@ tasks.integrationTest.finalizedBy(jacocoTestReport) // report is always generate check.dependsOn integrationTest dependencies { + implementation project(path: ":opensearch-resource-sharing-spi") implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}" diff --git a/settings.gradle b/settings.gradle index 1c3e7ff5aa..193587dee7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,6 @@ */ rootProject.name = 'opensearch-security' + +include "spi" +project(":spi").name = "opensearch-resource-sharing-spi" diff --git a/spi/README.md b/spi/README.md new file mode 100644 index 0000000000..ccd73db983 --- /dev/null +++ b/spi/README.md @@ -0,0 +1,152 @@ +# Resource Sharing and Access Control Plugin + +This plugin demonstrates resource sharing and access control functionality, providing APIs to create, manage, and verify access to resources. The plugin enables fine-grained permissions for sharing and accessing resources, making it suitable for systems requiring robust security and collaboration. + +## Features + +- Create and delete resources. +- Share resources with specific users, roles and/or backend_roles with specific scope(s). +- Revoke access to shared resources for a list of or all scopes. +- Verify access permissions for a given user within a given scope. +- List all resources accessible to current user. + +## API Endpoints + +The plugin exposes the following six API endpoints: + +### 1. Create Resource +- **Endpoint:** `POST /_plugins/sample_resource_sharing/create` +- **Description:** Creates a new resource. Also creates a resource sharing entry if security plugin is enabled. +- **Request Body:** + ```json + { + "name": "" + } + ``` +- **Response:** + ```json + { + "message": "Resource created successfully." + } + ``` + +### 2. Delete Resource +- **Endpoint:** `DELETE /_plugins/sample_resource_sharing/{resource_id}` +- **Description:** Deletes a specified resource owned by the requesting user. +- **Response:** + ```json + { + "message": "Resource deleted successfully." + } + ``` + +### 3. Share Resource +- **Endpoint:** `POST /_plugins/sample_resource_sharing/share` +- **Description:** Shares a resource with specified users or roles with defined scope. +- **Request Body:** + ```json + { + "resource_id" : "{{ADMIN_RESOURCE_ID}}", + "share_with" : { + "SAMPLE_FULL_ACCESS": { + "users": ["test"], + "roles": ["test_role"], + "backend_roles": ["test_backend_role"] + }, + "READ_ONLY": { + "users": ["test"], + "roles": ["test_role"], + "backend_roles": ["test_backend_role"] + }, + "READ_WRITE": { + "users": ["test"], + "roles": ["test_role"], + "backend_roles": ["test_backend_role"] + } + } + } + ``` +- **Response:** + ```json + { + "message": "Resource shared successfully." + } + ``` + +### 4. Revoke Access +- **Endpoint:** `POST /_plugins/sample_resource_sharing/revoke` +- **Description:** Revokes access to a resource for specified users or roles. +- **Request Body:** + ```json + { + "resource_id" : "", + "entities" : { + "users": ["test", "admin"], + "roles": ["test_role", "all_access"], + "backend_roles": ["test_backend_role", "admin"] + }, + "scopes": ["SAMPLE_FULL_ACCESS", "READ_ONLY", "READ_WRITE"] + } + ``` +- **Response:** + ```json + { + "message": "Resource access revoked successfully." + } + ``` + +### 5. Verify Access +- **Endpoint:** `GET /_plugins/sample_resource_sharing/verify_resource_access` +- **Description:** Verifies if a user or role has access to a specific resource with a specific scope. +- **Request Body:** + ```json + { + "resource_id": "", + "scope": "SAMPLE_FULL_ACCESS" + } + ``` +- **Response:** + ```json + { + "message": "User has requested scope SAMPLE_FULL_ACCESS access to " + } + ``` + +### 6. List Accessible Resources +- **Endpoint:** `GET /_plugins/sample_resource_sharing/list` +- **Description:** Lists all resources accessible to the requesting user or role. +- **Response:** + ```json + { + "resource-ids": [ + "", + "" + ] + } + ``` + +## Installation + +1. Clone the repository: + ```bash + git clone git@github.com:opensearch-project/security.git + ``` + +2. Navigate to the project directory: + ```bash + cd sample-resource-plugin + ``` + +3. Build and deploy the plugin: + ```bash + $ ./gradlew clean build -x test -x integrationTest -x spotbugsIntegrationTest + $ ./bin/opensearch-plugin install file: /sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-3.0.0.0-SNAPSHOT.zip + ``` + +## License + +This code is licensed under the Apache 2.0 License. + +## Copyright + +Copyright OpenSearch Contributors. diff --git a/spi/build.gradle b/spi/build.gradle new file mode 100644 index 0000000000..2cfe1a0d21 --- /dev/null +++ b/spi/build.gradle @@ -0,0 +1,74 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + id 'java' + id 'maven-publish' +} + +ext { + opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT") +} + +repositories { + mavenLocal() + mavenCentral() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } +} + +dependencies { + compileOnly "org.opensearch:opensearch:${opensearch_version}" + testImplementation "org.opensearch.test:framework:${opensearch_version}" +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +task sourcesJar(type: Jar) { + archiveClassifier.set 'sources' + from sourceSets.main.allJava +} + +task javadocJar(type: Jar) { + archiveClassifier.set 'javadoc' + from tasks.javadoc +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact sourcesJar + artifact javadocJar + pom { + name.set("OpenSearch Resource Sharing SPI") + description.set("OpenSearch Security Resource Sharing") + url.set("https://github.com/opensearch-project/security") + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + scm { + connection.set("scm:git@github.com:opensearch-project/security.git") + developerConnection.set("scm:git@github.com:opensearch-project/security.git") + url.set("https://github.com/opensearch-project/security.git") + } + developers { + developer { + name.set("OpenSearch Contributors") + url.set("https://github.com/opensearch-project") + } + } + } + } + } + repositories { + mavenLocal() + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java b/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java new file mode 100644 index 0000000000..9116ed0a9e --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.ToXContentFragment; + +/** + * Marker interface for all resources + */ +public interface Resource extends NamedWriteable, ToXContentFragment { + /** + * Get the resource name + * @return resource name + */ + String getResourceName(); + + // For de-serialization + Resource readFrom(StreamInput in) throws IOException; + + // TODO: Next iteration, check if getResourceType() should be implemented +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java new file mode 100644 index 0000000000..5f9c2558c2 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +/** + * This plugin allows to control access to resources. It is used by the ResourcePlugins to check whether a user has access to a resource defined by that plugin. + * It also defines java APIs to list, share or revoke resources with other users. + * User information will be fetched from the ThreadContext. + * + * @opensearch.experimental + */ +public interface ResourceAccessControlPlugin { + + boolean hasPermission(String resourceId, String resourceIndex, ResourceAccessScope> scope); +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java new file mode 100644 index 0000000000..b8dab4ff67 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +import java.util.Arrays; + +/** + * This interface defines the two basic access scopes for resource-access. + * Each plugin must implement their own scopes and manage them. + * These access scopes will then be used to verify the type of access being requested. + * + * @opensearch.experimental + */ +public interface ResourceAccessScope> { + String READ_ONLY = "read_only"; + String READ_WRITE = "read_write"; + + static & ResourceAccessScope> E fromValue(Class enumClass, String value) { + for (E enumConstant : enumClass.getEnumConstants()) { + if (enumConstant.value().equalsIgnoreCase(value)) { + return enumConstant; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + + String value(); + + static & ResourceAccessScope> String[] values(Class enumClass) { + return Arrays.stream(enumClass.getEnumConstants()).map(ResourceAccessScope::value).toArray(String[]::new); + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java new file mode 100644 index 0000000000..b3c2d0079d --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +import java.io.IOException; + +public interface ResourceParser { + /** + * Parse stringified json input to a desired Resource type + * @param source the stringified json input + * @return the parsed object of Resource type + * @throws IOException if something went wrong while parsing + */ + T parse(String source) throws IOException; +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceProvider.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceProvider.java new file mode 100644 index 0000000000..d6bde36a75 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceProvider.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +public class ResourceProvider { + private final String resourceType; + private final String resourceIndexName; + private final ResourceParser resourceParser; + + public ResourceParser getResourceParser() { + return resourceParser; + } + + public String getResourceIndexName() { + return resourceIndexName; + } + + public String getResourceType() { + return resourceType; + } + + public ResourceProvider(String resourceType, String resourceIndexName, ResourceParser resourceParser) { + this.resourceType = resourceType; + this.resourceIndexName = resourceIndexName; + this.resourceParser = resourceParser; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java new file mode 100644 index 0000000000..19d24b97e6 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.OpenSearchException; +import org.opensearch.common.inject.Inject; +import org.opensearch.security.spi.resources.fallback.DefaultResourceAccessControlPlugin; + +/** + * Service to get the current ResourceSharingExtension to perform authorization. + * + * @opensearch.experimental + */ +public class ResourceService { + private static final Logger log = LogManager.getLogger(ResourceService.class); + + private final ResourceAccessControlPlugin resourceACPlugin; + + @Inject + public ResourceService(final List resourceACPlugins) { + + if (resourceACPlugins.isEmpty()) { + log.info("Security plugin disabled: Using DefaultResourceAccessControlPlugin"); + resourceACPlugin = new DefaultResourceAccessControlPlugin(); + } else if (resourceACPlugins.size() == 1) { + log.info("Security plugin enabled: Using OpenSearchSecurityPlugin"); + resourceACPlugin = resourceACPlugins.get(0); + } else { + throw new OpenSearchException( + "Multiple resource access control plugins are not supported, found: " + + resourceACPlugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(",")) + ); + } + } + + /** + * Gets the ResourceAccessControlPlugin in-effect to perform authorization + */ + public ResourceAccessControlPlugin getResourceAccessControlPlugin() { + return resourceACPlugin; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java new file mode 100644 index 0000000000..f6eb1d35e8 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources; + +/** + * This interface should be implemented by all the plugins that define one or more resources. + * + * @opensearch.experimental + */ +public interface ResourceSharingExtension { + + /** + * Type of the resource + * @return a string containing the type of the resource + */ + String getResourceType(); + + /** + * The index where resource meta-data is stored + * @return the name of the parent index where resource meta-data is stored + */ + String getResourceIndex(); + + default ResourceParser getResourceParser() { + return null; + }; +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java b/spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java new file mode 100644 index 0000000000..379aa15d5d --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +package org.opensearch.security.spi.resources.fallback; + +import org.opensearch.security.spi.resources.ResourceAccessControlPlugin; +import org.opensearch.security.spi.resources.ResourceAccessScope; + +/** + * A default plugin for resource access control + */ +public class DefaultResourceAccessControlPlugin implements ResourceAccessControlPlugin { + /** + * @param resourceId the resource on which access is to be checked + * @param resourceIndex where the resource exists + * @param scope the scope being requested + * @return true always since this is a passthrough implementation + */ + @Override + public boolean hasPermission(String resourceId, String resourceIndex, ResourceAccessScope> scope) { + return true; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java b/spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java new file mode 100644 index 0000000000..2dd2803b38 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java @@ -0,0 +1,14 @@ +/* + * 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. + */ + +/** + * This package defines a pass-through implementation of ResourceAccessControlPlugin. + * + * @opensearch.experimental + */ +package main.java.org.opensearch.security.spi.resources.fallback; diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/package-info.java b/spi/src/main/java/org/opensearch/security/spi/resources/package-info.java new file mode 100644 index 0000000000..8990889429 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/package-info.java @@ -0,0 +1,14 @@ +/* + * 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. + */ + +/** + * This package defines class required to implement resource access control in OpenSearch. + * + * @opensearch.experimental + */ +package main.java.org.opensearch.security.spi.resources; diff --git a/spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java b/spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java new file mode 100644 index 0000000000..686f8484b9 --- /dev/null +++ b/spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java @@ -0,0 +1,123 @@ +/* + * 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. + */ + +package tests.java.opensearch.security.spi.resources; + +public class DefaultResourceAccessControlPluginTests { + // @Override + // protected Collection> nodePlugins() { + // return List.of(TestResourcePlugin.class); + // } + // + // public void testGetResources() throws IOException { + // final Client client = client(); + // + // createIndex(SAMPLE_TEST_INDEX); + // indexSampleDocuments(); + // + // Set resources; + // try ( + // DefaultResourceAccessControlExtension plugin = new DefaultResourceAccessControlExtension( + // client, + // internalCluster().getInstance(ThreadPool.class) + // ) + // ) { + // resources = plugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, TestResourcePlugin.TestResource.class); + // + // assertNotNull(resources); + // MatcherAssert.assertThat(resources, hasSize(2)); + // + // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("1")))); + // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2")))); + // } + // } + // + // public void testSampleResourcePluginListResources() throws IOException { + // createIndex(SAMPLE_TEST_INDEX); + // indexSampleDocuments(); + // + // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class)); + // + // Set resources = racPlugin.getAccessibleResourcesForCurrentUser( + // SAMPLE_TEST_INDEX, + // TestResourcePlugin.TestResource.class + // ); + // + // assertNotNull(resources); + // MatcherAssert.assertThat(resources, hasSize(2)); + // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("1")))); + // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2")))); + // } + // + // public void testSampleResourcePluginCallsHasPermission() { + // + // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class)); + // + // boolean canAccess = racPlugin.hasPermission("1", SAMPLE_TEST_INDEX, null); + // + // MatcherAssert.assertThat(canAccess, is(true)); + // + // } + // + // public void testSampleResourcePluginCallsShareWith() { + // + // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class)); + // + // ResourceSharing sharingInfo = racPlugin.shareWith("1", SAMPLE_TEST_INDEX, new ShareWith(Set.of())); + // + // MatcherAssert.assertThat(sharingInfo, is(nullValue())); + // } + // + // public void testSampleResourcePluginCallsRevokeAccess() { + // + // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class)); + // + // ResourceSharing sharingInfo = racPlugin.revokeAccess("1", SAMPLE_TEST_INDEX, Map.of(), Set.of("some_scope")); + // + // MatcherAssert.assertThat(sharingInfo, is(nullValue())); + // } + // + // public void testSampleResourcePluginCallsDeleteResourceSharingRecord() { + // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class)); + // + // boolean recordDeleted = racPlugin.deleteResourceSharingRecord("1", SAMPLE_TEST_INDEX); + // + // // no record to delete + // MatcherAssert.assertThat(recordDeleted, is(false)); + // } + // + // public void testSampleResourcePluginCallsDeleteAllResourceSharingRecordsForCurrentUser() { + // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class)); + // + // boolean recordDeleted = racPlugin.deleteAllResourceSharingRecordsForCurrentUser(); + // + // // no records to delete + // MatcherAssert.assertThat(recordDeleted, is(false)); + // } + // + // private void indexSampleDocuments() throws IOException { + // XContentBuilder doc1 = jsonBuilder().startObject().field("id", "1").field("name", "Test Document 1").endObject(); + // + // XContentBuilder doc2 = jsonBuilder().startObject().field("id", "2").field("name", "Test Document 2").endObject(); + // + // try (Client client = client()) { + // + // client.prepareIndex(SAMPLE_TEST_INDEX).setId("1").setSource(doc1).get(); + // + // client.prepareIndex(SAMPLE_TEST_INDEX).setId("2").setSource(doc2).get(); + // + // client.admin().indices().prepareRefresh(SAMPLE_TEST_INDEX).get(); + // } + // } +} diff --git a/spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java b/spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java new file mode 100644 index 0000000000..e537dc1697 --- /dev/null +++ b/spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java @@ -0,0 +1,220 @@ +/// * +// * 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. +// */ +// +// package tests.java.opensearch.security.spi.resources; +// +// import org.hamcrest.MatcherAssert; +// import org.mockito.Mock; +// import org.mockito.MockitoAnnotations; +// import org.opensearch.OpenSearchException; +// import org.opensearch.accesscontrol.resources.fallback.DefaultResourceAccessControlExtension; +// import org.opensearch.client.Client; +// import org.opensearch.plugins.ResourceAccessControlPlugin; +// import org.opensearch.plugins.ResourceSharingExtension; +// import org.opensearch.test.OpenSearchTestCase; +// import org.opensearch.threadpool.ThreadPool; +// +// import java.util.ArrayList; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; +// +// import static org.hamcrest.Matchers.*; +// import static org.mockito.Mockito.mock; +// +// public class ResourceServiceTests extends OpenSearchTestCase { +// +// @Mock +// private Client client; +// +// @Mock +// private ThreadPool threadPool; +// +// public void setup() { +// MockitoAnnotations.openMocks(this); +// } +// +// public void testGetResourceAccessControlPluginReturnsInitializedPlugin() { +// setup(); +// Client mockClient = mock(Client.class); +// ThreadPool mockThreadPool = mock(ThreadPool.class); +// +// ResourceAccessControlPlugin mockPlugin = mock(ResourceAccessControlPlugin.class); +// List plugins = new ArrayList<>(); +// plugins.add(mockPlugin); +// +// List resourcePlugins = new ArrayList<>(); +// +// ResourceService resourceService = new ResourceService(plugins, resourcePlugins, mockClient, mockThreadPool); +// +// ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin(); +// +// MatcherAssert.assertThat(mockPlugin, equalTo(result)); +// } +// +// public void testGetResourceAccessControlPlugin_NoPlugins() { +// setup(); +// List emptyPlugins = new ArrayList<>(); +// List resourcePlugins = new ArrayList<>(); +// +// ResourceService resourceService = new ResourceService(emptyPlugins, resourcePlugins, client, threadPool); +// +// ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin(); +// +// assertNotNull(result); +// MatcherAssert.assertThat(result, instanceOf(DefaultResourceAccessControlExtension.class)); +// } +// +// public void testGetResourceAccessControlPlugin_SinglePlugin() { +// setup(); +// ResourceAccessControlPlugin mockPlugin = mock(ResourceAccessControlPlugin.class); +// List singlePlugin = Arrays.asList(mockPlugin); +// List resourcePlugins = new ArrayList<>(); +// +// ResourceService resourceService = new ResourceService(singlePlugin, resourcePlugins, client, threadPool); +// +// ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin(); +// +// assertNotNull(result); +// assertSame(mockPlugin, result); +// } +// +// public void testListResourcePluginsReturnsPluginList() { +// setup(); +// List resourceACPlugins = new ArrayList<>(); +// List expectedResourcePlugins = new ArrayList<>(); +// expectedResourcePlugins.add(mock(ResourceSharingExtension.class)); +// expectedResourcePlugins.add(mock(ResourceSharingExtension.class)); +// +// ResourceService resourceService = new ResourceService(resourceACPlugins, expectedResourcePlugins, client, threadPool); +// +// List actualResourcePlugins = resourceService.listResourcePlugins(); +// +// MatcherAssert.assertThat(expectedResourcePlugins, equalTo(actualResourcePlugins)); +// } +// +// public void testListResourcePlugins_concurrentModification() { +// setup(); +// List emptyACPlugins = Collections.emptyList(); +// List resourcePlugins = new ArrayList<>(); +// resourcePlugins.add(mock(ResourceSharingExtension.class)); +// +// ResourceService resourceService = new ResourceService(emptyACPlugins, resourcePlugins, client, threadPool); +// +// Thread modifierThread = new Thread(() -> { resourcePlugins.add(mock(ResourceSharingExtension.class)); }); +// +// modifierThread.start(); +// +// List result = resourceService.listResourcePlugins(); +// +// assertNotNull(result); +// // The size could be either 1 or 2 depending on the timing of the concurrent modification +// assertTrue(result.size() == 1 || result.size() == 2); +// } +// +// public void testListResourcePlugins_emptyList() { +// setup(); +// List emptyACPlugins = Collections.emptyList(); +// List emptyResourcePlugins = Collections.emptyList(); +// +// ResourceService resourceService = new ResourceService(emptyACPlugins, emptyResourcePlugins, client, threadPool); +// +// List result = resourceService.listResourcePlugins(); +// +// assertNotNull(result); +// MatcherAssert.assertThat(result, is(empty())); +// } +// +// public void testListResourcePlugins_immutability() { +// setup(); +// List emptyACPlugins = Collections.emptyList(); +// List resourcePlugins = new ArrayList<>(); +// resourcePlugins.add(mock(ResourceSharingExtension.class)); +// +// ResourceService resourceService = new ResourceService(emptyACPlugins, resourcePlugins, client, threadPool); +// +// List result = resourceService.listResourcePlugins(); +// +// assertThrows(UnsupportedOperationException.class, () -> { result.add(mock(ResourceSharingExtension.class)); }); +// } +// +// public void testResourceServiceConstructorWithMultiplePlugins() { +// setup(); +// ResourceAccessControlPlugin plugin1 = mock(ResourceAccessControlPlugin.class); +// ResourceAccessControlPlugin plugin2 = mock(ResourceAccessControlPlugin.class); +// List resourceACPlugins = Arrays.asList(plugin1, plugin2); +// List resourcePlugins = Arrays.asList(mock(ResourceSharingExtension.class)); +// +// assertThrows(OpenSearchException.class, () -> { new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool); }); +// } +// +// public void testResourceServiceConstructor_MultiplePlugins() { +// setup(); +// ResourceAccessControlPlugin mockPlugin1 = mock(ResourceAccessControlPlugin.class); +// ResourceAccessControlPlugin mockPlugin2 = mock(ResourceAccessControlPlugin.class); +// List multiplePlugins = Arrays.asList(mockPlugin1, mockPlugin2); +// List resourcePlugins = new ArrayList<>(); +// +// assertThrows( +// org.opensearch.OpenSearchException.class, +// () -> { new ResourceService(multiplePlugins, resourcePlugins, client, threadPool); } +// ); +// } +// +// public void testResourceServiceWithMultipleResourceACPlugins() { +// setup(); +// List multipleResourceACPlugins = Arrays.asList( +// mock(ResourceAccessControlPlugin.class), +// mock(ResourceAccessControlPlugin.class) +// ); +// List resourcePlugins = new ArrayList<>(); +// +// assertThrows( +// OpenSearchException.class, +// () -> { new ResourceService(multipleResourceACPlugins, resourcePlugins, client, threadPool); } +// ); +// } +// +// public void testResourceServiceWithNoAccessControlPlugin() { +// setup(); +// List resourceACPlugins = new ArrayList<>(); +// List resourcePlugins = new ArrayList<>(); +// Client client = mock(Client.class); +// ThreadPool threadPool = mock(ThreadPool.class); +// +// ResourceService resourceService = new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool); +// +// MatcherAssert.assertThat(resourceService.getResourceAccessControlPlugin(), instanceOf(DefaultResourceAccessControlExtension.class)); +// MatcherAssert.assertThat(resourcePlugins, equalTo(resourceService.listResourcePlugins())); +// } +// +// public void testResourceServiceWithNoResourceACPlugins() { +// setup(); +// List emptyResourceACPlugins = new ArrayList<>(); +// List resourcePlugins = new ArrayList<>(); +// +// ResourceService resourceService = new ResourceService(emptyResourceACPlugins, resourcePlugins, client, threadPool); +// +// assertNotNull(resourceService.getResourceAccessControlPlugin()); +// } +// +// public void testResourceServiceWithSingleResourceAccessControlPlugin() { +// setup(); +// List resourceACPlugins = new ArrayList<>(); +// ResourceAccessControlPlugin mockPlugin = mock(ResourceAccessControlPlugin.class); +// resourceACPlugins.add(mockPlugin); +// +// List resourcePlugins = new ArrayList<>(); +// +// ResourceService resourceService = new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool); +// +// assertNotNull(resourceService); +// MatcherAssert.assertThat(mockPlugin, equalTo(resourceService.getResourceAccessControlPlugin())); +// MatcherAssert.assertThat(resourcePlugins, equalTo(resourceService.listResourcePlugins())); +// } +// } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 4153d9749d..14cd439566 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -69,7 +69,6 @@ import org.opensearch.OpenSearchSecurityException; import org.opensearch.SpecialPermission; import org.opensearch.Version; -import org.opensearch.accesscontrol.resources.*; import org.opensearch.action.ActionRequest; import org.opensearch.action.search.PitService; import org.opensearch.action.search.SearchScrollAction; @@ -118,12 +117,11 @@ import org.opensearch.indices.IndicesService; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.ClusterPlugin; +import org.opensearch.plugins.ExtensiblePlugin; import org.opensearch.plugins.ExtensionAwarePlugin; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.plugins.MapperPlugin; import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.ResourceAccessControlPlugin; -import org.opensearch.plugins.ResourcePlugin; import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; @@ -191,6 +189,12 @@ import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.setting.OpensearchDynamicSetting; import org.opensearch.security.setting.TransportPassiveAuthSetting; +import org.opensearch.security.spi.resources.Resource; +import org.opensearch.security.spi.resources.ResourceAccessControlPlugin; +import org.opensearch.security.spi.resources.ResourceAccessScope; +import org.opensearch.security.spi.resources.ResourceParser; +import org.opensearch.security.spi.resources.ResourceProvider; +import org.opensearch.security.spi.resources.ResourceSharingExtension; import org.opensearch.security.ssl.ExternalSecurityKeyStore; import org.opensearch.security.ssl.OpenSearchSecureSettingsFactory; import org.opensearch.security.ssl.OpenSearchSecuritySSLPlugin; @@ -244,7 +248,8 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin IdentityPlugin, ResourceAccessControlPlugin, // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings - ExtensionAwarePlugin + ExtensionAwarePlugin, + ExtensiblePlugin // CS-ENFORCE-SINGLE { @@ -283,6 +288,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private ResourceSharingIndexManagementRepository rmr; private ResourceAccessHandler resourceAccessHandler; private final Set indicesToListen = new HashSet<>(); + private static final Map resourceProviders = new HashMap<>(); public static boolean isActionTraceEnabled() { @@ -2121,13 +2127,6 @@ public void onNodeStarted(DiscoveryNode localNode) { // create resource sharing index if absent rmr.createResourceSharingIndexIfAbsent(); - for (ResourcePlugin resourcePlugin : OpenSearchSecurityPlugin.GuiceHolder.getResourceService().listResourcePlugins()) { - String resourceIndex = resourcePlugin.getResourceIndex(); - - this.indicesToListen.add(resourceIndex); - log.warn("Security plugin started listening to index: {} of plugin: {}", resourceIndex, resourcePlugin); - } - final Set securityModules = ReflectionHelper.getModulesLoaded(); log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); } @@ -2233,40 +2232,32 @@ private void tryAddSecurityProvider() { }); } - @Override - public Set getAccessibleResourcesForCurrentUser(String systemIndexName, Class clazz) { - return this.resourceAccessHandler.getAccessibleResourcesForCurrentUser(systemIndexName, clazz); + public static Map getResourceProviders() { + return resourceProviders; } @Override - public boolean hasPermission(String resourceId, String systemIndexName, String scope) { - return this.resourceAccessHandler.hasPermission(resourceId, systemIndexName, scope); + public boolean hasPermission(String resourceId, String resourceIndex, ResourceAccessScope> scope) { + return this.resourceAccessHandler.hasPermission(resourceId, resourceIndex, scope.value()); } + // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings @Override - public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { - return this.resourceAccessHandler.shareWith(resourceId, systemIndexName, shareWith); - } + public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) { - @Override - public ResourceSharing revokeAccess( - String resourceId, - String systemIndexName, - Map> entities, - Set scopes - ) { - return this.resourceAccessHandler.revokeAccess(resourceId, systemIndexName, entities, scopes); - } + for (ResourceSharingExtension extension : loader.loadExtensions(ResourceSharingExtension.class)) { + String resourceType = extension.getResourceType(); + String resourceIndexName = extension.getResourceIndex(); + ResourceParser resourceParser = extension.getResourceParser(); - @Override - public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { - return this.resourceAccessHandler.deleteResourceSharingRecord(resourceId, systemIndexName); - } + this.indicesToListen.add(resourceIndexName); - @Override - public boolean deleteAllResourceSharingRecordsForCurrentUser() { - return this.resourceAccessHandler.deleteAllResourceSharingRecordsForCurrentUser(); + ResourceProvider resourceProvider = new ResourceProvider(resourceType, resourceIndexName, resourceParser); + resourceProviders.put(resourceIndexName, resourceProvider); + log.info("Loaded resource provider extension: {}, index: {}", resourceType, resourceIndexName); + } } + // CS-ENFORCE-SINGLE public static class GuiceHolder implements LifecycleComponent { @@ -2274,7 +2265,6 @@ public static class GuiceHolder implements LifecycleComponent { private static RemoteClusterService remoteClusterService; private static IndicesService indicesService; private static PitService pitService; - private static ResourceService resourceService; // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions private static ExtensionsManager extensionsManager; @@ -2285,15 +2275,13 @@ public GuiceHolder( final TransportService remoteClusterService, IndicesService indicesService, PitService pitService, - ExtensionsManager extensionsManager, - ResourceService resourceService + ExtensionsManager extensionsManager ) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; GuiceHolder.pitService = pitService; GuiceHolder.extensionsManager = extensionsManager; - GuiceHolder.resourceService = resourceService; } // CS-ENFORCE-SINGLE @@ -2319,10 +2307,6 @@ public static ExtensionsManager getExtensionsManager() { } // CS-ENFORCE-SINGLE - public static ResourceService getResourceService() { - return resourceService; - } - @Override public void close() {} diff --git a/src/main/java/org/opensearch/security/resources/CreatedBy.java b/src/main/java/org/opensearch/security/resources/CreatedBy.java new file mode 100644 index 0000000000..3790d56a72 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/CreatedBy.java @@ -0,0 +1,89 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * This class is used to store information about the creator of a resource. + * Concrete implementation will be provided by security plugin + * + * @opensearch.experimental + */ +public class CreatedBy implements ToXContentFragment, NamedWriteable { + + private final String creatorType; + private final String creator; + + public CreatedBy(String creatorType, String creator) { + this.creatorType = creatorType; + this.creator = creator; + } + + public CreatedBy(StreamInput in) throws IOException { + this.creatorType = in.readString(); + this.creator = in.readString(); + } + + public String getCreator() { + return creator; + } + + public String getCreatorType() { + return creatorType; + } + + @Override + public String toString() { + return "CreatedBy {" + this.creatorType + "='" + this.creator + '\'' + '}'; + } + + @Override + public String getWriteableName() { + return "created_by"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(creatorType); + out.writeString(creator); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field(creatorType, creator).endObject(); + } + + public static CreatedBy fromXContent(XContentParser parser) throws IOException { + String creator = null; + String creatorType = null; + XContentParser.Token token; + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + creatorType = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_STRING) { + creator = parser.text(); + } + } + + if (creator == null) { + throw new IllegalArgumentException(creatorType + " is required"); + } + + return new CreatedBy(creatorType, creator); + } +} diff --git a/src/main/java/org/opensearch/security/resources/Creator.java b/src/main/java/org/opensearch/security/resources/Creator.java index 84a00756c1..c7a913d4de 100644 --- a/src/main/java/org/opensearch/security/resources/Creator.java +++ b/src/main/java/org/opensearch/security/resources/Creator.java @@ -1,3 +1,11 @@ +/* + * 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. + */ + package org.opensearch.security.resources; public enum Creator { diff --git a/src/main/java/org/opensearch/security/resources/Recipient.java b/src/main/java/org/opensearch/security/resources/Recipient.java index 7cd2ed76ad..354f75fc0f 100644 --- a/src/main/java/org/opensearch/security/resources/Recipient.java +++ b/src/main/java/org/opensearch/security/resources/Recipient.java @@ -1,3 +1,11 @@ +/* + * 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. + */ + package org.opensearch.security.resources; public enum Recipient { diff --git a/src/main/java/org/opensearch/security/resources/RecipientType.java b/src/main/java/org/opensearch/security/resources/RecipientType.java new file mode 100644 index 0000000000..6ed3004b7e --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/RecipientType.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +/** + * This class determines a type of recipient a resource can be shared with. + * An example type would be a user or a role. + * This class is used to determine the type of recipient a resource can be shared with. + * @opensearch.experimental + */ +public class RecipientType { + private final String type; + + public RecipientType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + @Override + public String toString() { + return type; + } +} diff --git a/src/main/java/org/opensearch/security/resources/RecipientTypeRegistry.java b/src/main/java/org/opensearch/security/resources/RecipientTypeRegistry.java new file mode 100644 index 0000000000..95da5debef --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/RecipientTypeRegistry.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class determines a collection of recipient types a resource can be shared with. + * + * @opensearch.experimental + */ +public class RecipientTypeRegistry { + private static final Map REGISTRY = new HashMap<>(); + + public static void registerRecipientType(String key, RecipientType recipientType) { + REGISTRY.put(key, recipientType); + } + + public static RecipientType fromValue(String value) { + RecipientType type = REGISTRY.get(value); + if (type == null) { + throw new IllegalArgumentException("Unknown RecipientType: " + value + ". Must be 1 of these: " + REGISTRY.values()); + } + return type; + } +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index eb9a81408d..149e058752 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -19,14 +19,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.RecipientType; -import org.opensearch.accesscontrol.resources.RecipientTypeRegistry; -import org.opensearch.accesscontrol.resources.Resource; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; -import org.opensearch.accesscontrol.resources.SharedWithScope; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.spi.resources.Resource; +import org.opensearch.security.spi.resources.ResourceParser; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; @@ -73,11 +70,12 @@ public void initializeRecipientTypes() { * @param resourceIndex The resource index to check for accessible resources. * @return A set of accessible resource IDs. */ - public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { - if (areArgumentsInvalid(resourceIndex, clazz)) { - return Collections.emptySet(); - } - final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + @SuppressWarnings("unchecked") + public Set getAccessibleResourcesForCurrentUser(String resourceIndex) { + validateArguments(resourceIndex); + ResourceParser parser = OpenSearchSecurityPlugin.getResourceProviders().get(resourceIndex).getResourceParser(); + + final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); return Collections.emptySet(); @@ -87,24 +85,24 @@ public Set getAccessibleResourcesForCurrentUser(String r // check if user is admin, if yes all resources should be accessible if (adminDNs.isAdmin(user)) { - return loadAllResources(resourceIndex, clazz); + return loadAllResources(resourceIndex, parser); } Set result = new HashSet<>(); // 0. Own resources - result.addAll(loadOwnResources(resourceIndex, user.getName(), clazz)); + result.addAll(loadOwnResources(resourceIndex, user.getName(), parser)); // 1. By username - result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString(), clazz)); + result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString(), parser)); // 2. By roles Set roles = user.getSecurityRoles(); - result.addAll(loadSharedWithResources(resourceIndex, roles, Recipient.ROLES.toString(), clazz)); + result.addAll(loadSharedWithResources(resourceIndex, roles, Recipient.ROLES.toString(), parser)); // 3. By backend_roles Set backendRoles = user.getRoles(); - result.addAll(loadSharedWithResources(resourceIndex, backendRoles, Recipient.BACKEND_ROLES.toString(), clazz)); + result.addAll(loadSharedWithResources(resourceIndex, backendRoles, Recipient.BACKEND_ROLES.toString(), parser)); return result; } @@ -118,10 +116,9 @@ public Set getAccessibleResourcesForCurrentUser(String r * @return True if the user has the specified permission, false otherwise. */ public boolean hasPermission(String resourceId, String resourceIndex, String scope) { - if (areArgumentsInvalid(resourceId, resourceIndex, scope)) { - return false; - } - final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + validateArguments(resourceId, resourceIndex, scope); + + final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId); @@ -160,10 +157,9 @@ public boolean hasPermission(String resourceId, String resourceIndex, String sco * @return The updated ResourceSharing document. */ public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) { - if (areArgumentsInvalid(resourceId, resourceIndex, shareWith)) { - return null; - } - final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + validateArguments(resourceId, resourceIndex, shareWith); + + final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString()); // check if user is admin, if yes the user has permission @@ -186,10 +182,8 @@ public ResourceSharing revokeAccess( Map> revokeAccess, Set scopes ) { - if (areArgumentsInvalid(resourceId, resourceIndex, revokeAccess, scopes)) { - return null; - } - final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + validateArguments(resourceId, resourceIndex, revokeAccess, scopes); + final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes); // check if user is admin, if yes the user has permission @@ -205,10 +199,9 @@ public ResourceSharing revokeAccess( * @return True if the record was successfully deleted, false otherwise. */ public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) { - if (areArgumentsInvalid(resourceId, resourceIndex)) { - return false; - } - final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + validateArguments(resourceId, resourceIndex); + + final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, resourceIndex, user.getName()); ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId); @@ -229,7 +222,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String resourceInd */ public boolean deleteAllResourceSharingRecordsForCurrentUser() { - final User user = threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName()); @@ -241,8 +234,8 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() { * @param resourceIndex The resource index to load resources from. * @return A set of resource IDs. */ - private Set loadAllResources(String resourceIndex, Class clazz) { - return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, clazz); + private Set loadAllResources(String resourceIndex, ResourceParser parser) { + return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, parser); } /** @@ -252,8 +245,8 @@ private Set loadAllResources(String resourceIndex, Class * @param userName The username of the owner. * @return A set of resource IDs owned by the user. */ - private Set loadOwnResources(String resourceIndex, String userName, Class clazz) { - return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, clazz); + private Set loadOwnResources(String resourceIndex, String userName, ResourceParser parser) { + return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, parser); } /** @@ -268,9 +261,9 @@ private Set loadSharedWithResources( String resourceIndex, Set entities, String RecipientType, - Class clazz + ResourceParser parser ) { - return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, RecipientType, clazz); + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, RecipientType, parser); } /** @@ -345,20 +338,19 @@ private boolean checkSharing(ResourceSharing document, Recipient recipient, Stri .orElse(false); // Return false if no matching scope is found } - private boolean areArgumentsInvalid(Object... args) { + private void validateArguments(Object... args) { if (args == null) { - return true; + throw new IllegalArgumentException("Arguments cannot be null"); } for (Object arg : args) { if (arg == null) { - return true; + throw new IllegalArgumentException("Argument cannot be null"); } // Additional check for String type arguments if (arg instanceof String && ((String) arg).trim().isEmpty()) { - return true; + throw new IllegalArgumentException("Arguments cannot be empty"); } } - return false; } } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharing.java b/src/main/java/org/opensearch/security/resources/ResourceSharing.java new file mode 100644 index 0000000000..6dd6734a87 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceSharing.java @@ -0,0 +1,207 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.io.IOException; +import java.util.Objects; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * Represents a resource sharing configuration that manages access control for OpenSearch resources. + * This class holds information about shared resources including their source, creator, and sharing permissions. + * + *

          This class implements {@link ToXContentFragment} for JSON serialization and {@link NamedWriteable} + * for stream-based serialization.

          + * + * The class maintains information about: + *
            + *
          • The source index where the resource is defined
          • + *
          • The unique identifier of the resource
          • + *
          • The creator's information
          • + *
          • The sharing permissions and recipients
          • + *
          + * + * + * @see org.opensearch.security.resources.CreatedBy + * @see org.opensearch.security.resources.ShareWith + * @opensearch.experimental + */ +public class ResourceSharing implements ToXContentFragment, NamedWriteable { + + /** + * The index where the resource is defined + */ + private String sourceIdx; + + /** + * The unique identifier of the resource + */ + private String resourceId; + + /** + * Information about who created the resource + */ + private CreatedBy createdBy; + + /** + * Information about with whom the resource is shared with + */ + private ShareWith shareWith; + + public ResourceSharing(String sourceIdx, String resourceId, CreatedBy createdBy, ShareWith shareWith) { + this.sourceIdx = sourceIdx; + this.resourceId = resourceId; + this.createdBy = createdBy; + this.shareWith = shareWith; + } + + public String getSourceIdx() { + return sourceIdx; + } + + public void setSourceIdx(String sourceIdx) { + this.sourceIdx = sourceIdx; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public CreatedBy getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(CreatedBy createdBy) { + this.createdBy = createdBy; + } + + public ShareWith getShareWith() { + return shareWith; + } + + public void setShareWith(ShareWith shareWith) { + this.shareWith = shareWith; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResourceSharing resourceSharing = (ResourceSharing) o; + return Objects.equals(getSourceIdx(), resourceSharing.getSourceIdx()) + && Objects.equals(getResourceId(), resourceSharing.getResourceId()) + && Objects.equals(getCreatedBy(), resourceSharing.getCreatedBy()) + && Objects.equals(getShareWith(), resourceSharing.getShareWith()); + } + + @Override + public int hashCode() { + return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getShareWith()); + } + + @Override + public String toString() { + return "Resource {" + + "sourceIdx='" + + sourceIdx + + '\'' + + ", resourceId='" + + resourceId + + '\'' + + ", createdBy=" + + createdBy + + ", sharedWith=" + + shareWith + + '}'; + } + + @Override + public String getWriteableName() { + return "resource_sharing"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(sourceIdx); + out.writeString(resourceId); + createdBy.writeTo(out); + if (shareWith != null) { + out.writeBoolean(true); + shareWith.writeTo(out); + } else { + out.writeBoolean(false); + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject().field("source_idx", sourceIdx).field("resource_id", resourceId).field("created_by"); + createdBy.toXContent(builder, params); + if (shareWith != null && !shareWith.getSharedWithScopes().isEmpty()) { + builder.field("share_with"); + shareWith.toXContent(builder, params); + } + return builder.endObject(); + } + + public static ResourceSharing fromXContent(XContentParser parser) throws IOException { + String sourceIdx = null; + String resourceId = null; + CreatedBy createdBy = null; + ShareWith shareWith = null; + + String currentFieldName = null; + XContentParser.Token token; + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else { + switch (Objects.requireNonNull(currentFieldName)) { + case "source_idx": + sourceIdx = parser.text(); + break; + case "resource_id": + resourceId = parser.text(); + break; + case "created_by": + createdBy = CreatedBy.fromXContent(parser); + break; + case "share_with": + shareWith = ShareWith.fromXContent(parser); + break; + default: + parser.skipChildren(); + break; + } + } + } + + validateRequiredField("source_idx", sourceIdx); + validateRequiredField("resource_id", resourceId); + validateRequiredField("created_by", createdBy); + + return new ResourceSharing(sourceIdx, resourceId, createdBy, shareWith); + } + + private static void validateRequiredField(String field, T value) { + if (value == null) { + throw new IllegalArgumentException(field + " is required"); + } + } +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 83341b1ff2..c44fe452d2 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -24,10 +24,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.CreatedBy; -import org.opensearch.accesscontrol.resources.RecipientType; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.OpenSearchException; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.get.MultiGetItemResponse; @@ -52,6 +49,7 @@ import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.IndexNotFoundException; import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MultiMatchQueryBuilder; import org.opensearch.index.query.QueryBuilders; @@ -67,6 +65,8 @@ import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.security.spi.resources.Resource; +import org.opensearch.security.spi.resources.ResourceParser; import org.opensearch.threadpool.ThreadPool; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; @@ -178,7 +178,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn return entry; } catch (Exception e) { LOGGER.info("Failed to create {} entry.", resourceSharingIndex, e); - return null; + throw new OpenSearchException("Failed to create " + resourceSharingIndex + " entry.", e); } } @@ -223,7 +223,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn *
        • Returns an empty list instead of throwing exceptions
        • *
        */ - public Set fetchAllDocuments(String pluginIndex, Class clazz) { + public Set fetchAllDocuments(String pluginIndex, ResourceParser parser) { LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); // TODO: Once stashContext is replaced with switchContext this call will have to be modified @@ -252,7 +252,7 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); - return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, clazz); + return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, parser); } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); @@ -327,9 +327,14 @@ public Set fetchAllDocuments(String pluginIndex, Class clazz) { * */ - public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String RecipientType, Class clazz) { + public Set fetchDocumentsForAllScopes( + String pluginIndex, + Set entities, + String RecipientType, + ResourceParser parser + ) { // "*" must match all scopes - return fetchDocumentsForAGivenScope(pluginIndex, entities, RecipientType, "*", clazz); + return fetchDocumentsForAGivenScope(pluginIndex, entities, RecipientType, "*", parser); } /** @@ -383,7 +388,7 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent *
      11. "roles" - for role-based access
      12. *
      13. "backend_roles" - for backend role-based access
      14. * - * @param scope The scope of the access. Should be implementation of {@link org.opensearch.accesscontrol.resources.ResourceAccessScope} + * @param scope The scope of the access. Should be implementation of {@link org.opensearch.security.spi.resources.ResourceAccessScope} * @param clazz Class to deserialize each document from Response into * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found @@ -399,12 +404,12 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set ent *
      15. Properly cleans up scroll context after use
      16. * */ - public Set fetchDocumentsForAGivenScope( + public Set fetchDocumentsForAGivenScope( String pluginIndex, Set entities, String RecipientType, String scope, - Class clazz + ResourceParser parser ) { LOGGER.debug( "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", @@ -445,7 +450,7 @@ public Set fetchDocumentsForAGivenScope( LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); - return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, clazz); + return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, parser); } catch (Exception e) { LOGGER.error( @@ -515,7 +520,7 @@ public Set fetchDocumentsForAGivenScope( * Set resources = fetchDocumentsByField("myIndex", "status", "active"); * */ - public Set fetchDocumentsByField(String pluginIndex, String field, String value, Class clazz) { + public Set fetchDocumentsByField(String pluginIndex, String field, String value, ResourceParser parser) { if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { throw new IllegalArgumentException("pluginIndex, field, and value must not be null or empty"); } @@ -538,7 +543,7 @@ public Set fetchDocumentsByField(String pluginIndex, String field, String LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); - return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, clazz); + return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, parser); } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); @@ -645,7 +650,7 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) } catch (Exception e) { LOGGER.error("Failed to fetch document for resourceId: {} from index: {}", resourceId, pluginIndex, e); - throw new RuntimeException("Failed to fetch document: " + e.getMessage(), e); + throw new OpenSearchException("Failed to fetch document for resourceId: " + resourceId + " from index: " + pluginIndex, e); } } @@ -726,7 +731,7 @@ public ResourceSharing updateResourceSharingInfo( } catch (IOException e) { LOGGER.error("Failed to build json content", e); - return null; + throw new OpenSearchException("Failed to build json content", e); } // Check if the user requesting to share is the owner of the resource @@ -734,7 +739,7 @@ public ResourceSharing updateResourceSharingInfo( ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); - return null; + throw new OpenSearchException("User " + requestUserName + " is not authorized to share resource " + resourceId); } CreatedBy createdBy; @@ -786,7 +791,12 @@ public ResourceSharing updateResourceSharingInfo( """, Collections.singletonMap("shareWith", shareWithMap)); boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); - return success ? new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith) : null; + if (!success) { + LOGGER.error("Failed to update resource sharing info for resource {}", resourceId); + throw new OpenSearchException("Failed to update resource sharing info for resource " + resourceId); + } + + return new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith); } /** @@ -942,7 +952,7 @@ public ResourceSharing revokeAccess( ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { LOGGER.error("User {} is not authorized to revoke access to resource {}", requestUserName, resourceId); - return null; + throw new OpenSearchException("User " + requestUserName + " is not authorized to revoke access to resource " + resourceId); } LOGGER.debug("Revoking access for resource {} in {} for entities: {} and scopes: {}", resourceId, sourceIdx, revokeAccess, scopes); @@ -1163,7 +1173,7 @@ public boolean deleteAllRecordsForUser(String name) { * @param clazz The class to deserialize the documents into. * @return A set of deserialized documents. */ - private Set getResourcesFromIds(Set resourceIds, String resourceIndex, Class clazz) { + private Set getResourcesFromIds(Set resourceIds, String resourceIndex, ResourceParser parser) { Set result = new HashSet<>(); // stashing Context to avoid permission issues in-case resourceIndex is a system index // TODO: Once stashContext is replaced with switchContext this call will have to be modified @@ -1178,12 +1188,17 @@ private Set getResourcesFromIds(Set resourceIds, String resourceI for (MultiGetItemResponse itemResponse : response.getResponses()) { if (!itemResponse.isFailed() && itemResponse.getResponse().isExists()) { String sourceAsString = itemResponse.getResponse().getSourceAsString(); - T resource = DefaultObjectMapper.readValue(sourceAsString, clazz); + // T resource = DefaultObjectMapper.readValue(sourceAsString, clazz); + T resource = parser.parse(sourceAsString); result.add(resource); } } + } catch (IndexNotFoundException e) { + LOGGER.error("Index {} does not exist", resourceIndex, e); + throw e; } catch (Exception e) { LOGGER.error("Failed to fetch resources with ids {} from index {}", resourceIds, resourceIndex, e); + throw new OpenSearchException("Failed to fetch resources: " + e.getMessage(), e); } return result; diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 58fe4cccf4..649a21dfb1 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -13,8 +13,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.accesscontrol.resources.CreatedBy; -import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.client.Client; import org.opensearch.core.index.shard.ShardId; import org.opensearch.index.engine.Engine; @@ -88,7 +86,7 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re String resourceId = index.id(); - User user = threadPool.getThreadContext().getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); + User user = (User) threadPool.getThreadContext().getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); try { ResourceSharing sharing = this.resourceSharingIndexHandler.indexResourceSharing( diff --git a/src/main/java/org/opensearch/security/resources/ShareWith.java b/src/main/java/org/opensearch/security/resources/ShareWith.java new file mode 100644 index 0000000000..2a8e047761 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ShareWith.java @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * + * This class contains information about whom a resource is shared with and at what scope. + * Example: + * "share_with": { + * "read_only": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * }, + * "read_write": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * } + * } + * + * @opensearch.experimental + */ +public class ShareWith implements ToXContentFragment, NamedWriteable { + + /** + * A set of objects representing the scopes and their associated users, roles, and backend roles. + */ + private final Set sharedWithScopes; + + public ShareWith(Set sharedWithScopes) { + this.sharedWithScopes = sharedWithScopes; + } + + public ShareWith(StreamInput in) throws IOException { + this.sharedWithScopes = in.readSet(SharedWithScope::new); + } + + public Set getSharedWithScopes() { + return sharedWithScopes; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + for (SharedWithScope scope : sharedWithScopes) { + scope.toXContent(builder, params); + } + + return builder.endObject(); + } + + public static ShareWith fromXContent(XContentParser parser) throws IOException { + Set sharedWithScopes = new HashSet<>(); + + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + parser.nextToken(); + } + + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + // Each field in the object represents a SharedWithScope + if (token == XContentParser.Token.FIELD_NAME) { + SharedWithScope scope = SharedWithScope.fromXContent(parser); + sharedWithScopes.add(scope); + } + } + + return new ShareWith(sharedWithScopes); + } + + @Override + public String getWriteableName() { + return "share_with"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeCollection(sharedWithScopes); + } + + @Override + public String toString() { + return "ShareWith " + sharedWithScopes; + } +} diff --git a/src/main/java/org/opensearch/security/resources/SharedWithScope.java b/src/main/java/org/opensearch/security/resources/SharedWithScope.java new file mode 100644 index 0000000000..02e3db854f --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/SharedWithScope.java @@ -0,0 +1,169 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * This class represents the scope at which a resource is shared with. + * Example: + * "read_only": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * } + * where "users", "roles" and "backend_roles" are the recipient entities + * + * @opensearch.experimental + */ +public class SharedWithScope implements ToXContentFragment, NamedWriteable { + + private final String scope; + + private final ScopeRecipients scopeRecipients; + + public SharedWithScope(String scope, ScopeRecipients scopeRecipients) { + this.scope = scope; + this.scopeRecipients = scopeRecipients; + } + + public SharedWithScope(StreamInput in) throws IOException { + this.scope = in.readString(); + this.scopeRecipients = new ScopeRecipients(in); + } + + public String getScope() { + return scope; + } + + public ScopeRecipients getSharedWithPerScope() { + return scopeRecipients; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(scope); + builder.startObject(); + + scopeRecipients.toXContent(builder, params); + + return builder.endObject(); + } + + public static SharedWithScope fromXContent(XContentParser parser) throws IOException { + String scope = parser.currentName(); + + parser.nextToken(); + + ScopeRecipients scopeRecipients = ScopeRecipients.fromXContent(parser); + + return new SharedWithScope(scope, scopeRecipients); + } + + @Override + public String toString() { + return "{" + scope + ": " + scopeRecipients + '}'; + } + + @Override + public String getWriteableName() { + return "shared_with_scope"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(scope); + out.writeNamedWriteable(scopeRecipients); + } + + /** + * This class represents the entities with whom a resource is shared with for a given scope. + * + * @opensearch.experimental + */ + public static class ScopeRecipients implements ToXContentFragment, NamedWriteable { + + private final Map> recipients; + + public ScopeRecipients(Map> recipients) { + if (recipients == null) { + throw new IllegalArgumentException("Recipients map cannot be null"); + } + this.recipients = recipients; + } + + public ScopeRecipients(StreamInput in) throws IOException { + this.recipients = in.readMap( + key -> RecipientTypeRegistry.fromValue(key.readString()), + input -> input.readSet(StreamInput::readString) + ); + } + + public Map> getRecipients() { + return recipients; + } + + @Override + public String getWriteableName() { + return "scope_recipients"; + } + + public static ScopeRecipients fromXContent(XContentParser parser) throws IOException { + Map> recipients = new HashMap<>(); + + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + String fieldName = parser.currentName(); + RecipientType recipientType = RecipientTypeRegistry.fromValue(fieldName); + + parser.nextToken(); + Set values = new HashSet<>(); + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + values.add(parser.text()); + } + recipients.put(recipientType, values); + } + } + + return new ScopeRecipients(recipients); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap( + recipients, + (streamOutput, recipientType) -> streamOutput.writeString(recipientType.getType()), + (streamOutput, strings) -> streamOutput.writeCollection(strings, StreamOutput::writeString) + ); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (recipients.isEmpty()) { + return builder; + } + for (Map.Entry> entry : recipients.entrySet()) { + builder.array(entry.getKey().getType(), entry.getValue().toArray()); + } + return builder; + } + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java new file mode 100644 index 0000000000..3a8aa6ae59 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.list; + +import org.opensearch.action.ActionType; + +/** + * Action to list resources + */ +public class ListAccessibleResourcesAction extends ActionType { + + public static final ListAccessibleResourcesAction INSTANCE = new ListAccessibleResourcesAction(); + + public static final String NAME = "cluster:admin/security/resources/list"; + + private ListAccessibleResourcesAction() { + super(NAME, ListAccessibleResourcesResponse::new); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java new file mode 100644 index 0000000000..f16887f12b --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.list; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +/** + * Request object for ListSampleResource transport action + */ +public class ListAccessibleResourcesRequest extends ActionRequest { + + private String resourceIndex; + + public ListAccessibleResourcesRequest(String resourceIndex) { + this.resourceIndex = resourceIndex; + } + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public ListAccessibleResourcesRequest(final StreamInput in) throws IOException { + this.resourceIndex = in.readString(); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(this.resourceIndex); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceIndex() { + return resourceIndex; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java new file mode 100644 index 0000000000..1a678ac2ce --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.list; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.security.spi.resources.Resource; + +/** + * Response to a ListAccessibleResourcesRequest + */ +public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { + private final Set resources; + + public ListAccessibleResourcesResponse(Set resources) { + this.resources = resources; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeCollection(resources); + } + + public ListAccessibleResourcesResponse(StreamInput in) { + // TODO need to fix this to return correct value + this.resources = new HashSet<>(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("resources", resources); + builder.endObject(); + return builder; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java new file mode 100644 index 0000000000..61935ee709 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.list; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class RestListAccessibleResourcesAction extends BaseRestHandler { + + public RestListAccessibleResourcesAction() {} + + @Override + public List routes() { + return addRoutesPrefix(ImmutableList.of(new Route(GET, "/resources/list")), PLUGIN_ROUTE_PREFIX); + } + + @Override + public String getName() { + return "list_accessible_resources"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceIndex = (String) source.get("resource_index"); + final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(resourceIndex); + return channel -> client.executeLocally( + ListAccessibleResourcesAction.INSTANCE, + listAccessibleResourcesRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java new file mode 100644 index 0000000000..2bde557884 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java @@ -0,0 +1,74 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.revoke; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.security.resources.RecipientType; +import org.opensearch.security.resources.RecipientTypeRegistry; + +import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class RestRevokeResourceAccessAction extends BaseRestHandler { + + public RestRevokeResourceAccessAction() {} + + @Override + public List routes() { + return addRoutesPrefix(ImmutableList.of(new Route(POST, "/resources/revoke")), PLUGIN_ROUTE_PREFIX); + } + + @Override + public String getName() { + return "revoke_resources_access"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + String resourceIndex = (String) source.get("resource_index"); + @SuppressWarnings("unchecked") + Map> revokeSource = (Map>) source.get("entities"); + Map> revoke = revokeSource.entrySet() + .stream() + .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); + @SuppressWarnings("unchecked") + Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); + final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest( + resourceId, + resourceIndex, + revoke, + scopes + ); + return channel -> client.executeLocally( + RevokeResourceAccessAction.INSTANCE, + revokeResourceAccessRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java new file mode 100644 index 0000000000..e27ce05a2b --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.revoke; + +import org.opensearch.action.ActionType; + +public class RevokeResourceAccessAction extends ActionType { + public static final RevokeResourceAccessAction INSTANCE = new RevokeResourceAccessAction(); + + public static final String NAME = "cluster:admin/security/resources/revoke"; + + private RevokeResourceAccessAction() { + super(NAME, RevokeResourceAccessResponse::new); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java new file mode 100644 index 0000000000..667f1670dd --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.revoke; + +import java.io.IOException; +import java.util.Map; +import java.util.Set; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.security.resources.RecipientType; + +public class RevokeResourceAccessRequest extends ActionRequest { + + private final String resourceId; + private final String resourceIndex; + private final Map> revokeAccess; + private final Set scopes; + + public RevokeResourceAccessRequest( + String resourceId, + String resourceIndex, + Map> revokeAccess, + Set scopes + ) { + this.resourceId = resourceId; + this.resourceIndex = resourceIndex; + this.revokeAccess = revokeAccess; + this.scopes = scopes; + } + + public RevokeResourceAccessRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.resourceIndex = in.readString(); + this.revokeAccess = in.readMap(input -> new RecipientType(input.readString()), input -> input.readSet(StreamInput::readString)); + this.scopes = in.readSet(StreamInput::readString); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeString(resourceIndex); + out.writeMap( + revokeAccess, + (streamOutput, recipientType) -> streamOutput.writeString(recipientType.getType()), + StreamOutput::writeStringCollection + ); + out.writeStringCollection(scopes); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public String getResourceIndex() { + return resourceIndex; + } + + public Map> getRevokeAccess() { + return revokeAccess; + } + + public Set getScopes() { + return scopes; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java new file mode 100644 index 0000000000..090dfb54d0 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.revoke; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class RevokeResourceAccessResponse extends ActionResponse implements ToXContentObject { + private final String message; + + public RevokeResourceAccessResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + public RevokeResourceAccessResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java b/src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java new file mode 100644 index 0000000000..3559ced3aa --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.share; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.security.resources.ShareWith; + +import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class RestShareResourceAction extends BaseRestHandler { + + public RestShareResourceAction() {} + + @Override + public List routes() { + return addRoutesPrefix(ImmutableList.of(new Route(POST, "/resources/share")), PLUGIN_ROUTE_PREFIX); + } + + @Override + public String getName() { + return "share_resources"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + String resourceIndex = (String) source.get("resource_index"); + + ShareWith shareWith = parseShareWith(source); + final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, resourceIndex, shareWith); + return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); + } + + private ShareWith parseShareWith(Map source) throws IOException { + @SuppressWarnings("unchecked") + Map shareWithMap = (Map) source.get("share_with"); + if (shareWithMap == null || shareWithMap.isEmpty()) { + throw new IllegalArgumentException("share_with is required and cannot be empty"); + } + + String jsonString = XContentFactory.jsonBuilder().map(shareWithMap).toString(); + + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, jsonString) + ) { + return ShareWith.fromXContent(parser); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid share_with structure: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java new file mode 100644 index 0000000000..a112108bf1 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.share; + +import org.opensearch.action.ActionType; + +/** + * Share resource + */ +public class ShareResourceAction extends ActionType { + + public static final ShareResourceAction INSTANCE = new ShareResourceAction(); + + public static final String NAME = "cluster:admin/security/resources/share"; + + private ShareResourceAction() { + super(NAME, ShareResourceResponse::new); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java new file mode 100644 index 0000000000..560e2967ba --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.share; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.security.resources.ShareWith; + +public class ShareResourceRequest extends ActionRequest { + + private final String resourceId; + private final String resourceIndex; + private final ShareWith shareWith; + + public ShareResourceRequest(String resourceId, String resourceIndex, ShareWith shareWith) { + this.resourceId = resourceId; + this.resourceIndex = resourceIndex; + this.shareWith = shareWith; + } + + public ShareResourceRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.resourceIndex = in.readString(); + this.shareWith = in.readNamedWriteable(ShareWith.class); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeString(resourceIndex); + out.writeNamedWriteable(shareWith); + } + + @Override + public ActionRequestValidationException validate() { + + return null; + } + + public String getResourceId() { + return resourceId; + } + + public String getResourceIndex() { + return resourceIndex; + } + + public ShareWith getShareWith() { + return shareWith; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java new file mode 100644 index 0000000000..15b83c8d6f --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.share; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class ShareResourceResponse extends ActionResponse implements ToXContentObject { + private final String message; + + public ShareResourceResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + public ShareResourceResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java new file mode 100644 index 0000000000..3a7e713a83 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java @@ -0,0 +1,59 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.verify; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class RestVerifyResourceAccessAction extends BaseRestHandler { + + public RestVerifyResourceAccessAction() {} + + @Override + public List routes() { + return addRoutesPrefix(ImmutableList.of(new Route(GET, "/resources/verify_access")), PLUGIN_ROUTE_PREFIX); + } + + @Override + public String getName() { + return "verify_resource_access"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + String resourceIndex = (String) source.get("resource_index"); + String scope = (String) source.get("scope"); + + final VerifyResourceAccessRequest verifyResourceAccessRequest = new VerifyResourceAccessRequest(resourceId, resourceIndex, scope); + return channel -> client.executeLocally( + VerifyResourceAccessAction.INSTANCE, + verifyResourceAccessRequest, + new RestToXContentListener<>(channel) + ); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java new file mode 100644 index 0000000000..ff07b1e455 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.verify; + +import org.opensearch.action.ActionType; + +/** + * Action to verify resource access for current user + */ +public class VerifyResourceAccessAction extends ActionType { + + public static final VerifyResourceAccessAction INSTANCE = new VerifyResourceAccessAction(); + + public static final String NAME = "cluster:admin/sample-resource-plugin/verify/resource_access"; + + private VerifyResourceAccessAction() { + super(NAME, VerifyResourceAccessResponse::new); + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java new file mode 100644 index 0000000000..529db51830 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java @@ -0,0 +1,69 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.verify; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +public class VerifyResourceAccessRequest extends ActionRequest { + + private final String resourceId; + + private final String resourceIndex; + + private final String scope; + + /** + * Default constructor + */ + public VerifyResourceAccessRequest(String resourceId, String resourceIndex, String scope) { + this.resourceId = resourceId; + this.resourceIndex = resourceIndex; + this.scope = scope; + } + + /** + * Constructor with stream input + * @param in the stream input + * @throws IOException IOException + */ + public VerifyResourceAccessRequest(final StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.resourceIndex = in.readString(); + this.scope = in.readString(); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + out.writeString(resourceIndex); + out.writeString(scope); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getResourceId() { + return resourceId; + } + + public String getResourceIndex() { + return resourceIndex; + } + + public String getScope() { + return scope; + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java new file mode 100644 index 0000000000..a7fa7a2de4 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access.verify; + +import java.io.IOException; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; + +public class VerifyResourceAccessResponse extends ActionResponse implements ToXContentObject { + private final String message; + + /** + * Default constructor + * + * @param message The message + */ + public VerifyResourceAccessResponse(String message) { + this.message = message; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(message); + } + + /** + * Constructor with StreamInput + * + * @param in the stream input + */ + public VerifyResourceAccessResponse(final StreamInput in) throws IOException { + message = in.readString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/ListAccessibleResourcesTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/ListAccessibleResourcesTransportAction.java new file mode 100644 index 0000000000..0e3ca2f1c4 --- /dev/null +++ b/src/main/java/org/opensearch/security/transport/resources/access/ListAccessibleResourcesTransportAction.java @@ -0,0 +1,56 @@ +/* + * 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. + */ + +package org.opensearch.security.transport.resources.access; + +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; +import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesRequest; +import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesResponse; +import org.opensearch.security.spi.resources.Resource; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class ListAccessibleResourcesTransportAction extends HandledTransportAction< + ListAccessibleResourcesRequest, + ListAccessibleResourcesResponse> { + private static final Logger log = LogManager.getLogger(ListAccessibleResourcesTransportAction.class); + private final ResourceAccessHandler resourceAccessHandler; + + @Inject + public ListAccessibleResourcesTransportAction( + TransportService transportService, + ActionFilters actionFilters, + ResourceAccessHandler resourceAccessHandler + ) { + super(ListAccessibleResourcesAction.NAME, transportService, actionFilters, ListAccessibleResourcesRequest::new); + this.resourceAccessHandler = resourceAccessHandler; + } + + @Override + protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { + try { + Set resources = resourceAccessHandler.getAccessibleResourcesForCurrentUser(request.getResourceIndex()); + log.info("Successfully fetched accessible resources for current user : {}", resources); + listener.onResponse(new ListAccessibleResourcesResponse(resources)); + } catch (Exception e) { + log.info("Failed to list accessible resources for current user: ", e); + listener.onFailure(e); + } + + } +} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java new file mode 100644 index 0000000000..fd7324dca1 --- /dev/null +++ b/src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java @@ -0,0 +1,65 @@ +/* + * 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. + */ + +package org.opensearch.security.transport.resources.access; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.OpenSearchException; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.resources.ResourceSharing; +import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; +import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessRequest; +import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class RevokeResourceAccessTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(RevokeResourceAccessTransportAction.class); + private final ResourceAccessHandler resourceAccessHandler; + + @Inject + public RevokeResourceAccessTransportAction( + TransportService transportService, + ActionFilters actionFilters, + ResourceAccessHandler resourceAccessHandler + ) { + super(RevokeResourceAccessAction.NAME, transportService, actionFilters, RevokeResourceAccessRequest::new); + this.resourceAccessHandler = resourceAccessHandler; + } + + @Override + protected void doExecute(Task task, RevokeResourceAccessRequest request, ActionListener listener) { + try { + ResourceSharing revoke = revokeAccess(request); + if (revoke == null) { + log.error("Failed to revoke access to resource {}", request.getResourceId()); + listener.onFailure(new OpenSearchException("Failed to revoke access to resource " + request.getResourceId())); + return; + } + log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); + listener.onResponse(new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.")); + } catch (Exception e) { + listener.onFailure(e); + } + } + + private ResourceSharing revokeAccess(RevokeResourceAccessRequest request) { + return this.resourceAccessHandler.revokeAccess( + request.getResourceId(), + request.getResourceIndex(), + request.getRevokeAccess(), + request.getScopes() + ); + } +} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java new file mode 100644 index 0000000000..1d8111f1b6 --- /dev/null +++ b/src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package org.opensearch.security.transport.resources.access; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.OpenSearchException; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.resources.ResourceSharing; +import org.opensearch.security.rest.resources.access.share.ShareResourceAction; +import org.opensearch.security.rest.resources.access.share.ShareResourceRequest; +import org.opensearch.security.rest.resources.access.share.ShareResourceResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class ShareResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class); + private final ResourceAccessHandler resourceAccessHandler; + + @Inject + public ShareResourceTransportAction( + TransportService transportService, + ActionFilters actionFilters, + ResourceAccessHandler resourceAccessHandler + ) { + super(ShareResourceAction.NAME, transportService, actionFilters, ShareResourceRequest::new); + this.resourceAccessHandler = resourceAccessHandler; + } + + @Override + protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { + ResourceSharing sharing = null; + try { + sharing = shareResource(request); + if (sharing == null) { + log.error("Failed to share resource {}", request.getResourceId()); + listener.onFailure(new OpenSearchException("Failed to share resource " + request.getResourceId())); + return; + } + log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); + listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); + } catch (Exception e) { + listener.onFailure(e); + } + } + + private ResourceSharing shareResource(ShareResourceRequest request) throws Exception { + return this.resourceAccessHandler.shareWith(request.getResourceId(), request.getResourceIndex(), request.getShareWith()); + } +} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java new file mode 100644 index 0000000000..f608453c02 --- /dev/null +++ b/src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java @@ -0,0 +1,66 @@ +/* + * 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. + */ + +package org.opensearch.security.transport.resources.access; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; +import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessRequest; +import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessResponse; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class VerifyResourceAccessTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class); + private final ResourceAccessHandler resourceAccessHandler; + + @Inject + public VerifyResourceAccessTransportAction( + TransportService transportService, + ActionFilters actionFilters, + Client nodeClient, + ResourceAccessHandler resourceAccessHandler + ) { + super(VerifyResourceAccessAction.NAME, transportService, actionFilters, VerifyResourceAccessRequest::new); + this.resourceAccessHandler = resourceAccessHandler; + } + + @Override + protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) { + try { + boolean hasRequestedScopeAccess = this.resourceAccessHandler.hasPermission( + request.getResourceId(), + request.getResourceIndex(), + request.getScope() + ); + + StringBuilder sb = new StringBuilder(); + sb.append("User "); + sb.append(hasRequestedScopeAccess ? "has" : "does not have"); + sb.append(" requested scope "); + sb.append(request.getScope()); + sb.append(" access to "); + sb.append(request.getResourceId()); + + log.info(sb.toString()); + listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); + } catch (Exception e) { + log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); + listener.onFailure(e); + } + } + +} diff --git a/src/main/java/org/opensearch/security/util/ResourceValidation.java b/src/main/java/org/opensearch/security/util/ResourceValidation.java new file mode 100644 index 0000000000..3850087e4e --- /dev/null +++ b/src/main/java/org/opensearch/security/util/ResourceValidation.java @@ -0,0 +1,34 @@ +/* + * 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. + */ + +package org.opensearch.security.util; + +import java.util.HashSet; +import java.util.Set; + +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.security.spi.resources.ResourceAccessScope; + +public class ResourceValidation { + public static ActionRequestValidationException validateScopes(Set scopes) { + Set validScopes = new HashSet<>(); + validScopes.add(ResourceAccessScope.READ_ONLY); + validScopes.add(ResourceAccessScope.READ_WRITE); + + // TODO See if we can add custom scopes as part of this validation routine + + for (String s : scopes) { + if (!validScopes.contains(s)) { + ActionRequestValidationException exception = new ActionRequestValidationException(); + exception.addValidationError("Invalid scope: " + s + ". Scope must be one of: " + validScopes); + return exception; + } + } + return null; + } +} diff --git a/src/test/java/org/opensearch/security/resources/CreatedByTests.java b/src/test/java/org/opensearch/security/resources/CreatedByTests.java new file mode 100644 index 0000000000..6b183ccbc7 --- /dev/null +++ b/src/test/java/org/opensearch/security/resources/CreatedByTests.java @@ -0,0 +1,286 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.io.IOException; + +import org.hamcrest.MatcherAssert; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CreatedByTests extends OpenSearchTestCase { + + private static final String CREATOR_TYPE = "user"; + + public void testCreatedByConstructorWithValidUser() { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + + MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getCreator()))); + } + + public void testCreatedByFromStreamInput() throws IOException { + String expectedUser = "testUser"; + + try (BytesStreamOutput out = new BytesStreamOutput()) { + out.writeString(CREATOR_TYPE); + out.writeString(expectedUser); + + StreamInput in = out.bytes().streamInput(); + + CreatedBy createdBy = new CreatedBy(in); + + MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getCreator()))); + } + } + + public void testCreatedByWithEmptyStreamInput() throws IOException { + + try (StreamInput mockStreamInput = mock(StreamInput.class)) { + when(mockStreamInput.readString()).thenThrow(new IOException("EOF")); + + assertThrows(IOException.class, () -> new CreatedBy(mockStreamInput)); + } + } + + public void testCreatedByWithEmptyUser() { + + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, ""); + MatcherAssert.assertThat("", equalTo(createdBy.getCreator())); + } + + public void testCreatedByWithIOException() throws IOException { + + try (StreamInput mockStreamInput = mock(StreamInput.class)) { + when(mockStreamInput.readString()).thenThrow(new IOException("Test IOException")); + + assertThrows(IOException.class, () -> new CreatedBy(mockStreamInput)); + } + } + + public void testCreatedByWithLongUsername() { + String longUsername = "a".repeat(10000); + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, longUsername); + MatcherAssert.assertThat(longUsername, equalTo(createdBy.getCreator())); + } + + public void testCreatedByWithUnicodeCharacters() { + String unicodeUsername = "用户こんにちは"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, unicodeUsername); + MatcherAssert.assertThat(unicodeUsername, equalTo(createdBy.getCreator())); + } + + public void testFromXContentThrowsExceptionWhenUserFieldIsMissing() throws IOException { + String emptyJson = "{}"; + IllegalArgumentException exception; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, emptyJson)) { + + exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + + MatcherAssert.assertThat("null is required", equalTo(exception.getMessage())); + } + + public void testFromXContentWithEmptyInput() throws IOException { + String emptyJson = "{}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, emptyJson)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + public void testFromXContentWithExtraFields() throws IOException { + String jsonWithExtraFields = "{\"user\": \"testUser\", \"extraField\": \"value\"}"; + XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithExtraFields); + + CreatedBy.fromXContent(parser); + } + + public void testFromXContentWithIncorrectFieldType() throws IOException { + String jsonWithIncorrectType = "{\"user\": 12345}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithIncorrectType)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + public void testFromXContentWithEmptyUser() throws IOException { + String emptyJson = "{\"" + CREATOR_TYPE + "\": \"\" }"; + CreatedBy createdBy; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, emptyJson)) { + parser.nextToken(); + + createdBy = CreatedBy.fromXContent(parser); + } + + MatcherAssert.assertThat(CREATOR_TYPE, equalTo(createdBy.getCreatorType())); + MatcherAssert.assertThat("", equalTo(createdBy.getCreator())); + } + + public void testFromXContentWithNullUserValue() throws IOException { + String jsonWithNullUser = "{\"user\": null}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithNullUser)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + public void testFromXContentWithValidUser() throws IOException { + String json = "{\"user\":\"testUser\"}"; + XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, json); + + CreatedBy createdBy = CreatedBy.fromXContent(parser); + + MatcherAssert.assertThat(createdBy, notNullValue()); + MatcherAssert.assertThat("testUser", equalTo(createdBy.getCreator())); + } + + public void testGetCreatorReturnsCorrectValue() { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + + String actualUser = createdBy.getCreator(); + + MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); + } + + public void testGetCreatorWithNullString() { + + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, null); + MatcherAssert.assertThat(createdBy.getCreator(), nullValue()); + } + + public void testGetWriteableNameReturnsCorrectString() { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, "testUser"); + MatcherAssert.assertThat("created_by", equalTo(createdBy.getWriteableName())); + } + + public void testToStringWithEmptyUser() { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, ""); + String result = createdBy.toString(); + MatcherAssert.assertThat("CreatedBy {user=''}", equalTo(result)); + } + + public void testToStringWithNullUser() { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, (String) null); + String result = createdBy.toString(); + MatcherAssert.assertThat("CreatedBy {user='null'}", equalTo(result)); + } + + public void testToStringWithLongUserName() { + + String longUserName = "a".repeat(1000); + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, longUserName); + String result = createdBy.toString(); + MatcherAssert.assertThat(result.startsWith("CreatedBy {user='"), is(true)); + MatcherAssert.assertThat(result.endsWith("'}"), is(true)); + MatcherAssert.assertThat(1019, equalTo(result.length())); + } + + public void testToXContentWithEmptyUser() throws IOException { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, ""); + XContentBuilder builder = JsonXContent.contentBuilder(); + + createdBy.toXContent(builder, null); + String result = builder.toString(); + MatcherAssert.assertThat("{\"user\":\"\"}", equalTo(result)); + } + + public void testWriteToWithExceptionInStreamOutput() throws IOException { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, "user1"); + try (StreamOutput failingOutput = new StreamOutput() { + @Override + public void writeByte(byte b) throws IOException { + throw new IOException("Simulated IO exception"); + } + + @Override + public void writeBytes(byte[] b, int offset, int length) throws IOException { + throw new IOException("Simulated IO exception"); + } + + @Override + public void flush() throws IOException { + + } + + @Override + public void close() throws IOException { + + } + + @Override + public void reset() throws IOException { + + } + }) { + + assertThrows(IOException.class, () -> createdBy.writeTo(failingOutput)); + } + } + + public void testWriteToWithLongUserName() throws IOException { + String longUserName = "a".repeat(65536); + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, longUserName); + BytesStreamOutput out = new BytesStreamOutput(); + createdBy.writeTo(out); + MatcherAssert.assertThat(out.size(), greaterThan(65536)); + } + + public void test_createdByToStringReturnsCorrectFormat() { + String testUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, testUser); + + String expected = "CreatedBy {user='" + testUser + "'}"; + String actual = createdBy.toString(); + + MatcherAssert.assertThat(expected, equalTo(actual)); + } + + public void test_toXContent_serializesCorrectly() throws IOException { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + XContentBuilder builder = XContentFactory.jsonBuilder(); + + createdBy.toXContent(builder, null); + + String expectedJson = "{\"user\":\"testUser\"}"; + MatcherAssert.assertThat(expectedJson, equalTo(builder.toString())); + } + + public void test_writeTo_writesUserCorrectly() throws IOException { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + + BytesStreamOutput out = new BytesStreamOutput(); + createdBy.writeTo(out); + + StreamInput in = out.bytes().streamInput(); + in.readString(); + String actualUser = in.readString(); + + MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); + } + +} diff --git a/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java b/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java new file mode 100644 index 0000000000..394bae608e --- /dev/null +++ b/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import org.hamcrest.MatcherAssert; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +public class RecipientTypeRegistryTests extends OpenSearchTestCase { + + public void testFromValue() { + RecipientTypeRegistry.registerRecipientType("ble1", new RecipientType("ble1")); + RecipientTypeRegistry.registerRecipientType("ble2", new RecipientType("ble2")); + + // Valid Value + RecipientType type = RecipientTypeRegistry.fromValue("ble1"); + assertNotNull(type); + assertEquals("ble1", type.getType()); + + // Invalid Value + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RecipientTypeRegistry.fromValue("bleble")); + MatcherAssert.assertThat("Unknown RecipientType: bleble. Must be 1 of these: [ble1, ble2]", is(equalTo(exception.getMessage()))); + } +} diff --git a/src/test/java/org/opensearch/security/resources/ShareWithTests.java b/src/test/java/org/opensearch/security/resources/ShareWithTests.java new file mode 100644 index 0000000000..7c7b634e86 --- /dev/null +++ b/src/test/java/org/opensearch/security/resources/ShareWithTests.java @@ -0,0 +1,263 @@ +/* + * 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. + */ + +package org.opensearch.security.resources; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hamcrest.MatcherAssert; +import org.junit.Before; + +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.security.spi.resources.ResourceAccessScope; +import org.opensearch.test.OpenSearchTestCase; + +import org.mockito.Mockito; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ShareWithTests extends OpenSearchTestCase { + + @Before + public void setupResourceRecipientTypes() { + initializeRecipientTypes(); + } + + public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOException { + String json = "{\"read_only\": {\"users\": [\"user1\"], \"roles\": [], \"backend_roles\": []}}"; + XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, json); + + parser.nextToken(); + + ShareWith shareWith = ShareWith.fromXContent(parser); + + assertNotNull(shareWith); + Set sharedWithScopes = shareWith.getSharedWithScopes(); + assertNotNull(sharedWithScopes); + MatcherAssert.assertThat(1, equalTo(sharedWithScopes.size())); + + SharedWithScope scope = sharedWithScopes.iterator().next(); + MatcherAssert.assertThat("read_only", equalTo(scope.getScope())); + + SharedWithScope.ScopeRecipients scopeRecipients = scope.getSharedWithPerScope(); + assertNotNull(scopeRecipients); + Map> recipients = scopeRecipients.getRecipients(); + MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(1)); + MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())), contains("user1")); + MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), is(0)); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), + is(0) + ); + } + + public void testFromXContentWithEmptyInput() throws IOException { + String emptyJson = "{}"; + XContentParser parser = XContentType.JSON.xContent().createParser(xContentRegistry(), null, emptyJson); + + ShareWith result = ShareWith.fromXContent(parser); + + assertNotNull(result); + MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); + } + + public void testFromXContentWithStartObject() throws IOException { + XContentParser parser; + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject() + .startObject(ResourceAccessScope.READ_ONLY) + .array("users", "user1", "user2") + .array("roles", "role1") + .array("backend_roles", "backend_role1") + .endObject() + .startObject(ResourceAccessScope.READ_WRITE) + .array("users", "user3") + .array("roles", "role2", "role3") + .array("backend_roles") + .endObject() + .endObject(); + + parser = JsonXContent.jsonXContent.createParser(null, null, builder.toString()); + } + + parser.nextToken(); + + ShareWith shareWith = ShareWith.fromXContent(parser); + + assertNotNull(shareWith); + Set scopes = shareWith.getSharedWithScopes(); + MatcherAssert.assertThat(scopes.size(), equalTo(2)); + + for (SharedWithScope scope : scopes) { + SharedWithScope.ScopeRecipients perScope = scope.getSharedWithPerScope(); + Map> recipients = perScope.getRecipients(); + if (scope.getScope().equals(ResourceAccessScope.READ_ONLY)) { + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), + is(2) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), + is(1) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), + is(1) + ); + } else if (scope.getScope().equals(ResourceAccessScope.READ_WRITE)) { + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), + is(1) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), + is(2) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), + is(0) + ); + } + } + } + + public void testFromXContentWithUnexpectedEndOfInput() throws IOException { + XContentParser mockParser = mock(XContentParser.class); + when(mockParser.currentToken()).thenReturn(XContentParser.Token.START_OBJECT); + when(mockParser.nextToken()).thenReturn(XContentParser.Token.END_OBJECT, (XContentParser.Token) null); + + ShareWith result = ShareWith.fromXContent(mockParser); + + assertNotNull(result); + MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); + } + + public void testToXContentBuildsCorrectly() throws IOException { + SharedWithScope scope = new SharedWithScope( + "scope1", + new SharedWithScope.ScopeRecipients(Map.of(new RecipientType("users"), Set.of("bleh"))) + ); + + Set scopes = new HashSet<>(); + scopes.add(scope); + + ShareWith shareWith = new ShareWith(scopes); + + XContentBuilder builder = JsonXContent.contentBuilder(); + + shareWith.toXContent(builder, ToXContent.EMPTY_PARAMS); + + String result = builder.toString(); + + String expected = "{\"scope1\":{\"users\":[\"bleh\"]}}"; + + MatcherAssert.assertThat(expected.length(), equalTo(result.length())); + MatcherAssert.assertThat(expected, equalTo(result)); + } + + public void testWriteToWithEmptySet() throws IOException { + Set emptySet = Collections.emptySet(); + ShareWith shareWith = new ShareWith(emptySet); + StreamOutput mockOutput = Mockito.mock(StreamOutput.class); + + shareWith.writeTo(mockOutput); + + verify(mockOutput).writeCollection(emptySet); + } + + public void testWriteToWithIOException() throws IOException { + Set set = new HashSet<>(); + set.add(new SharedWithScope("test", new SharedWithScope.ScopeRecipients(Map.of()))); + ShareWith shareWith = new ShareWith(set); + StreamOutput mockOutput = Mockito.mock(StreamOutput.class); + + doThrow(new IOException("Simulated IO exception")).when(mockOutput).writeCollection(set); + + assertThrows(IOException.class, () -> shareWith.writeTo(mockOutput)); + } + + public void testWriteToWithLargeSet() throws IOException { + Set largeSet = new HashSet<>(); + for (int i = 0; i < 10000; i++) { + largeSet.add(new SharedWithScope("scope" + i, new SharedWithScope.ScopeRecipients(Map.of()))); + } + ShareWith shareWith = new ShareWith(largeSet); + StreamOutput mockOutput = Mockito.mock(StreamOutput.class); + + shareWith.writeTo(mockOutput); + + verify(mockOutput).writeCollection(largeSet); + } + + public void test_fromXContent_emptyObject() throws IOException { + XContentParser parser; + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject().endObject(); + parser = XContentType.JSON.xContent().createParser(null, null, builder.toString()); + } + + ShareWith shareWith = ShareWith.fromXContent(parser); + + MatcherAssert.assertThat(shareWith.getSharedWithScopes(), is(empty())); + } + + public void test_writeSharedWithScopesToStream() throws IOException { + StreamOutput mockStreamOutput = Mockito.mock(StreamOutput.class); + + Set sharedWithScopes = new HashSet<>(); + sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.READ_ONLY, new SharedWithScope.ScopeRecipients(Map.of()))); + sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.READ_WRITE, new SharedWithScope.ScopeRecipients(Map.of()))); + + ShareWith shareWith = new ShareWith(sharedWithScopes); + + shareWith.writeTo(mockStreamOutput); + + verify(mockStreamOutput, times(1)).writeCollection(eq(sharedWithScopes)); + } + + private void initializeRecipientTypes() { + RecipientTypeRegistry.registerRecipientType("users", new RecipientType("users")); + RecipientTypeRegistry.registerRecipientType("roles", new RecipientType("roles")); + RecipientTypeRegistry.registerRecipientType("backend_roles", new RecipientType("backend_roles")); + } +} + +enum DefaultRecipientType { + USERS("users"), + ROLES("roles"), + BACKEND_ROLES("backend_roles"); + + private final String name; + + DefaultRecipientType(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java b/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java index 0f7d5c59c5..d12fafb247 100644 --- a/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java +++ b/src/test/java/org/opensearch/security/transport/SecurityInterceptorTests.java @@ -20,7 +20,6 @@ import org.junit.Test; import org.opensearch.Version; -import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.action.search.PitService; import org.opensearch.cluster.ClusterName; import org.opensearch.cluster.node.DiscoveryNode; @@ -172,8 +171,7 @@ public void setup() { transportService, mock(IndicesService.class), mock(PitService.class), - mock(ExtensionsManager.class), - mock(ResourceService.class) + mock(ExtensionsManager.class) ); // CS-ENFORCE-SINGLE From 9b508de92f3595a6b88b1f484fb8227d90200ba4 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 8 Jan 2025 18:21:18 -0500 Subject: [PATCH 067/122] Adds a bunch of REST APIs and modifies DLS query to support resource permission filter Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 59 ++++++-- .../SecurityFlsDlsIndexSearcherWrapper.java | 40 ++++- .../resources/ResourceAccessHandler.java | 78 ++++++---- .../ResourceSharingIndexHandler.java | 141 +++++++++--------- .../list/ListAccessibleResourcesRequest.java | 2 +- .../list/ListAccessibleResourcesResponse.java | 24 ++- .../verify/VerifyResourceAccessAction.java | 2 +- ...ansportListAccessibleResourcesAction.java} | 10 +- ... TransportRevokeResourceAccessAction.java} | 6 +- ...java => TransportShareResourceAction.java} | 6 +- ... TransportVerifyResourceAccessAction.java} | 6 +- 11 files changed, 246 insertions(+), 128 deletions(-) rename src/main/java/org/opensearch/security/transport/resources/access/{ListAccessibleResourcesTransportAction.java => TransportListAccessibleResourcesAction.java} (83%) rename src/main/java/org/opensearch/security/transport/resources/access/{RevokeResourceAccessTransportAction.java => TransportRevokeResourceAccessAction.java} (92%) rename src/main/java/org/opensearch/security/transport/resources/access/{ShareResourceTransportAction.java => TransportShareResourceAction.java} (92%) rename src/main/java/org/opensearch/security/transport/resources/access/{VerifyResourceAccessTransportAction.java => TransportVerifyResourceAccessAction.java} (92%) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 14cd439566..a5f2bdfea4 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -58,6 +58,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -185,6 +187,14 @@ import org.opensearch.security.rest.SecurityInfoAction; import org.opensearch.security.rest.SecurityWhoAmIAction; import org.opensearch.security.rest.TenantInfoAction; +import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; +import org.opensearch.security.rest.resources.access.list.RestListAccessibleResourcesAction; +import org.opensearch.security.rest.resources.access.revoke.RestRevokeResourceAccessAction; +import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; +import org.opensearch.security.rest.resources.access.share.RestShareResourceAction; +import org.opensearch.security.rest.resources.access.share.ShareResourceAction; +import org.opensearch.security.rest.resources.access.verify.RestVerifyResourceAccessAction; +import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.setting.OpensearchDynamicSetting; @@ -212,6 +222,10 @@ import org.opensearch.security.transport.DefaultInterClusterRequestEvaluator; import org.opensearch.security.transport.InterClusterRequestEvaluator; import org.opensearch.security.transport.SecurityInterceptor; +import org.opensearch.security.transport.resources.access.TransportListAccessibleResourcesAction; +import org.opensearch.security.transport.resources.access.TransportRevokeResourceAccessAction; +import org.opensearch.security.transport.resources.access.TransportShareResourceAction; +import org.opensearch.security.transport.resources.access.TransportVerifyResourceAccessAction; import org.opensearch.security.user.User; import org.opensearch.security.user.UserService; import org.opensearch.tasks.Task; @@ -288,7 +302,8 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private ResourceSharingIndexManagementRepository rmr; private ResourceAccessHandler resourceAccessHandler; private final Set indicesToListen = new HashSet<>(); - private static final Map resourceProviders = new HashMap<>(); + private static final Map RESOURCE_PROVIDERS = new HashMap<>(); + private static final Set RESOURCE_INDICES = new HashSet<>(); public static boolean isActionTraceEnabled() { @@ -679,6 +694,16 @@ public List getRestHandlers( passwordHasher ) ); + + // Adds rest handlers for resource-access-control actions + handlers.addAll( + List.of( + new RestShareResourceAction(), + new RestRevokeResourceAccessAction(), + new RestListAccessibleResourcesAction(), + new RestVerifyResourceAccessAction() + ) + ); log.debug("Added {} rest handler(s)", handlers.size()); } } @@ -706,6 +731,16 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre actions.add(new ActionHandler<>(CertificatesActionType.INSTANCE, TransportCertificatesInfoNodesAction.class)); } actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class)); + + // Resource-access-control related actions + actions.addAll( + List.of( + new ActionHandler<>(ShareResourceAction.INSTANCE, TransportShareResourceAction.class), + new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, TransportRevokeResourceAccessAction.class), + new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, TransportListAccessibleResourcesAction.class), + new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, TransportVerifyResourceAccessAction.class) + ) + ); } return actions; } @@ -730,15 +765,17 @@ public void onIndexModule(IndexModule indexModule) { ciol, evaluator, dlsFlsValve::getCurrentConfig, - dlsFlsBaseContext + dlsFlsBaseContext, + resourceAccessHandler ) ); - if (this.indicesToListen.contains(indexModule.getIndex().getName())) { - ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance(); - resourceSharingIndexListener.initialize(threadPool, localClient, auditLog); + // Listening on POST and DELETE operations in resource indices + ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance(); + resourceSharingIndexListener.initialize(threadPool, localClient, auditLog); + if (RESOURCE_INDICES.contains(indexModule.getIndex().getName())) { indexModule.addIndexOperationListener(resourceSharingIndexListener); - log.warn("Security plugin started listening to operations on index {}", indexModule.getIndex().getName()); + log.warn("Security plugin started listening to operations on resource-index {}", indexModule.getIndex().getName()); } indexModule.forceQueryCacheProvider((indexSettings, nodeCache) -> new QueryCache() { @@ -2233,7 +2270,11 @@ private void tryAddSecurityProvider() { } public static Map getResourceProviders() { - return resourceProviders; + return ImmutableMap.copyOf(RESOURCE_PROVIDERS); + } + + public static Set getResourceIndices() { + return ImmutableSet.copyOf(RESOURCE_INDICES); } @Override @@ -2250,10 +2291,10 @@ public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) { String resourceIndexName = extension.getResourceIndex(); ResourceParser resourceParser = extension.getResourceParser(); - this.indicesToListen.add(resourceIndexName); + RESOURCE_INDICES.add(resourceIndexName); ResourceProvider resourceProvider = new ResourceProvider(resourceType, resourceIndexName, resourceParser); - resourceProviders.put(resourceIndexName, resourceProvider); + RESOURCE_PROVIDERS.put(resourceIndexName, resourceProvider); log.info("Loaded resource provider extension: {}, index: {}", resourceType, resourceIndexName); } } diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java index 4f7a412097..bb06604829 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java @@ -34,6 +34,7 @@ import org.opensearch.index.mapper.SeqNoFieldMapper; import org.opensearch.index.query.QueryShardContext; import org.opensearch.index.shard.ShardUtils; +import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.compliance.ComplianceIndexingOperationListener; import org.opensearch.security.privileges.DocumentAllowList; @@ -45,8 +46,11 @@ import org.opensearch.security.privileges.dlsfls.DlsRestriction; import org.opensearch.security.privileges.dlsfls.FieldMasking; import org.opensearch.security.privileges.dlsfls.FieldPrivileges; +import org.opensearch.security.resources.ResourceAccessHandler; import org.opensearch.security.support.ConfigConstants; +import joptsimple.internal.Strings; + public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapper { public final Logger log = LogManager.getLogger(this.getClass()); @@ -61,6 +65,7 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapp private final LongSupplier nowInMillis; private final Supplier dlsFlsProcessedConfigSupplier; private final DlsFlsBaseContext dlsFlsBaseContext; + private final ResourceAccessHandler resourceAccessHandler; public SecurityFlsDlsIndexSearcherWrapper( final IndexService indexService, @@ -71,7 +76,8 @@ public SecurityFlsDlsIndexSearcherWrapper( final ComplianceIndexingOperationListener ciol, final PrivilegesEvaluator evaluator, final Supplier dlsFlsProcessedConfigSupplier, - final DlsFlsBaseContext dlsFlsBaseContext + final DlsFlsBaseContext dlsFlsBaseContext, + final ResourceAccessHandler resourceAccessHandler ) { super(indexService, settings, adminDNs, evaluator); Set metadataFieldsCopy; @@ -103,6 +109,7 @@ public SecurityFlsDlsIndexSearcherWrapper( log.debug("FLS/DLS {} enabled for index {}", this, indexService.index().getName()); this.dlsFlsProcessedConfigSupplier = dlsFlsProcessedConfigSupplier; this.dlsFlsBaseContext = dlsFlsBaseContext; + this.resourceAccessHandler = resourceAccessHandler; } @SuppressWarnings("unchecked") @@ -116,7 +123,36 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm log.trace("dlsFlsWrap(); index: {}; privilegeEvaluationContext: {}", index.getName(), privilegesEvaluationContext); } - if (isAdmin || privilegesEvaluationContext == null) { + String indexName = shardId != null ? shardId.getIndexName() : null; + Set resourceIds = null; + if (!Strings.isNullOrEmpty(indexName) && OpenSearchSecurityPlugin.getResourceIndices().contains(indexName)) { + resourceIds = this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(indexName); + if (resourceIds.isEmpty()) { + return new EmptyFilterLeafReader.EmptyDirectoryReader(reader); + } + // Create a resource DLS query for the current user + QueryShardContext queryShardContext = this.indexService.newQueryShardContext(shardId.getId(), null, nowInMillis, null); + Query resourceQuery = this.resourceAccessHandler.createResourceDlsQuery(resourceIds, queryShardContext); + + // TODO the FlsRule must still be checked + return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( + reader, + FieldPrivileges.FlsRule.ALLOW_ALL, + resourceQuery, + indexService, + threadContext, + clusterService, + auditlog, + FieldMasking.FieldMaskingRule.ALLOW_ALL, + shardId, + metaFields + ); + } + + // resourceIds == null indicates that the index is not a resource index + // resourceIds.isEmpty() indicates that the index is a resource index but the user does not have access to any resource under the + // index + if (isAdmin || privilegesEvaluationContext == null || resourceIds == null) { return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( reader, FieldPrivileges.FlsRule.ALLOW_ALL, diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 149e058752..361342e611 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -11,6 +11,7 @@ package org.opensearch.security.resources; +import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -18,8 +19,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.lucene.search.Query; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.ConstantScoreQueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.index.query.QueryShardContext; import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.spi.resources.Resource; @@ -65,16 +71,11 @@ public void initializeRecipientTypes() { } /** - * Returns a set of accessible resources for the current user within the specified resource index. - * + * Returns a set of accessible resource IDs for the current user within the specified resource index. * @param resourceIndex The resource index to check for accessible resources. * @return A set of accessible resource IDs. */ - @SuppressWarnings("unchecked") - public Set getAccessibleResourcesForCurrentUser(String resourceIndex) { - validateArguments(resourceIndex); - ResourceParser parser = OpenSearchSecurityPlugin.getResourceProviders().get(resourceIndex).getResourceParser(); - + public Set getAccessibleResourceIdsForCurrentUser(String resourceIndex) { final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { LOGGER.info("Unable to fetch user details "); @@ -83,28 +84,45 @@ public Set getAccessibleResourcesForCurrentUser(String r LOGGER.info("Listing accessible resources within a resource index {} for : {}", resourceIndex, user.getName()); + Set resourceIds = new HashSet<>(); + // check if user is admin, if yes all resources should be accessible if (adminDNs.isAdmin(user)) { - return loadAllResources(resourceIndex, parser); + resourceIds.addAll(loadAllResources(resourceIndex)); + return resourceIds; } - Set result = new HashSet<>(); - // 0. Own resources - result.addAll(loadOwnResources(resourceIndex, user.getName(), parser)); + resourceIds.addAll(loadOwnResources(resourceIndex, user.getName())); // 1. By username - result.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString(), parser)); + resourceIds.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString())); // 2. By roles Set roles = user.getSecurityRoles(); - result.addAll(loadSharedWithResources(resourceIndex, roles, Recipient.ROLES.toString(), parser)); + resourceIds.addAll(loadSharedWithResources(resourceIndex, roles, Recipient.ROLES.toString())); // 3. By backend_roles Set backendRoles = user.getRoles(); - result.addAll(loadSharedWithResources(resourceIndex, backendRoles, Recipient.BACKEND_ROLES.toString(), parser)); + resourceIds.addAll(loadSharedWithResources(resourceIndex, backendRoles, Recipient.BACKEND_ROLES.toString())); + + return resourceIds; + } - return result; + /** + * Returns a set of accessible resources for the current user within the specified resource index. + * + * @param resourceIndex The resource index to check for accessible resources. + * @return A set of accessible resource IDs. + */ + @SuppressWarnings("unchecked") + public Set getAccessibleResourcesForCurrentUser(String resourceIndex) { + validateArguments(resourceIndex); + ResourceParser parser = OpenSearchSecurityPlugin.getResourceProviders().get(resourceIndex).getResourceParser(); + Set resourceIds = getAccessibleResourceIdsForCurrentUser(resourceIndex); + return resourceIds.isEmpty() + ? Set.of() + : this.resourceSharingIndexHandler.getResourceDocumentsFromIds(resourceIds, resourceIndex, parser); } /** @@ -234,8 +252,8 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() { * @param resourceIndex The resource index to load resources from. * @return A set of resource IDs. */ - private Set loadAllResources(String resourceIndex, ResourceParser parser) { - return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, parser); + private Set loadAllResources(String resourceIndex) { + return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex); } /** @@ -245,8 +263,8 @@ private Set loadAllResources(String resourceIndex, Resou * @param userName The username of the owner. * @return A set of resource IDs owned by the user. */ - private Set loadOwnResources(String resourceIndex, String userName, ResourceParser parser) { - return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, parser); + private Set loadOwnResources(String resourceIndex, String userName) { + return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName); } /** @@ -257,13 +275,8 @@ private Set loadOwnResources(String resourceIndex, Strin * @param RecipientType The type of entity (e.g., users, roles, backend_roles). * @return A set of resource IDs shared with the specified entities. */ - private Set loadSharedWithResources( - String resourceIndex, - Set entities, - String RecipientType, - ResourceParser parser - ) { - return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, RecipientType, parser); + private Set loadSharedWithResources(String resourceIndex, Set entities, String RecipientType) { + return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, RecipientType); } /** @@ -353,4 +366,17 @@ private void validateArguments(Object... args) { } } + /** + * Creates a DLS query for the given resource IDs. + * @param resourceIds The resource IDs to create the query for. + * @param queryShardContext The query shard context. + * @return The DLS query. + * @throws IOException If an I/O error occurs. + */ + public Query createResourceDlsQuery(Set resourceIds, QueryShardContext queryShardContext) throws IOException { + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + boolQueryBuilder.filter(QueryBuilders.termsQuery("_id", resourceIds)); + ConstantScoreQueryBuilder builder = new ConstantScoreQueryBuilder(boolQueryBuilder); + return builder.toQuery(queryShardContext); + } } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index c44fe452d2..7d4a55b8ca 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -25,6 +25,7 @@ import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; +import org.opensearch.action.DocWriteRequest; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.create.CreateIndexResponse; import org.opensearch.action.get.MultiGetItemResponse; @@ -42,7 +43,6 @@ import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.NamedXContentRegistry; @@ -66,6 +66,7 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.spi.resources.Resource; +import org.opensearch.security.spi.resources.ResourceAccessScope; import org.opensearch.security.spi.resources.ResourceParser; import org.opensearch.threadpool.ThreadPool; @@ -166,6 +167,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn IndexRequest ir = client.prepareIndex(resourceSharingIndex) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .setSource(entry.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .setOpType(DocWriteRequest.OpType.CREATE) // only create if an entry doesn't exist .request(); ActionListener irListener = ActionListener.wrap(idxResponse -> { @@ -183,47 +185,47 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn } /** - * Fetches all resource sharing records that match the specified system index. This method retrieves - * a list of resource IDs associated with the given system index from the resource sharing index. - * - *

        The method executes the following steps: - *

          - *
        1. Creates a search request with term query matching the system index
        2. - *
        3. Applies source filtering to only fetch resource_id field
        4. - *
        5. Executes the search with a limit of 10000 documents
        6. - *
        7. Processes the results to extract resource IDs
        8. - *
        - * - *

        Example query structure: - *

        -        * {
        -        *   "query": {
        -        *     "term": {
        -        *       "source_idx": "system_index_name"
        -        *     }
        -        *   },
        -        *   "_source": ["resource_id"],
        -        *   "size": 10000
        -        * }
        -        * 
        - * - * @param pluginIndex The source index to match against the source_idx field - * @return Set containing resource IDs that belong to the specified system index. - * Returns an empty list if: - *
          - *
        • No matching documents are found
        • - *
        • An error occurs during the search operation
        • - *
        • The system index parameter is invalid
        • - *
        - * - * @apiNote This method: - *
          - *
        • Uses source filtering for optimal performance
        • - *
        • Performs exact matching on the source_idx field
        • - *
        • Returns an empty list instead of throwing exceptions
        • - *
        - */ - public Set fetchAllDocuments(String pluginIndex, ResourceParser parser) { + * Fetches all resource sharing records that match the specified system index. This method retrieves + * a list of resource IDs associated with the given system index from the resource sharing index. + * + *

        The method executes the following steps: + *

          + *
        1. Creates a search request with term query matching the system index
        2. + *
        3. Applies source filtering to only fetch resource_id field
        4. + *
        5. Executes the search with a limit of 10000 documents
        6. + *
        7. Processes the results to extract resource IDs
        8. + *
        + * + *

        Example query structure: + *

        +    * {
        +    *   "query": {
        +    *     "term": {
        +    *       "source_idx": "resource_index_name"
        +    *     }
        +    *   },
        +    *   "_source": ["resource_id"],
        +    *   "size": 10000
        +    * }
        +    * 
        + * + * @param pluginIndex The source index to match against the source_idx field + * @return Set containing resource IDs that belong to the specified system index. + * Returns an empty list if: + *
          + *
        • No matching documents are found
        • + *
        • An error occurs during the search operation
        • + *
        • The system index parameter is invalid
        • + *
        + * + * @apiNote This method: + *
          + *
        • Uses source filtering for optimal performance
        • + *
        • Performs exact matching on the source_idx field
        • + *
        • Returns an empty list instead of throwing exceptions
        • + *
        + */ + public Set fetchAllDocuments(String pluginIndex) { LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); // TODO: Once stashContext is replaced with switchContext this call will have to be modified @@ -252,7 +254,7 @@ public Set fetchAllDocuments(String pluginIndex, Resourc LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); - return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, parser); + return resourceIds; } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); @@ -279,7 +281,7 @@ public Set fetchAllDocuments(String pluginIndex, Resourc * "query": { * "bool": { * "must": [ - * { "term": { "source_idx": "system_index_name" } }, + * { "term": { "source_idx": "resource_index_name" } }, * { * "bool": { * "should": [ @@ -311,7 +313,6 @@ public Set fetchAllDocuments(String pluginIndex, Resourc *
      17. "roles" - for role-based access
      18. *
      19. "backend_roles" - for backend role-based access
      20. * - * @param clazz Class to deserialize each document from Response into * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found * @@ -327,14 +328,9 @@ public Set fetchAllDocuments(String pluginIndex, Resourc * */ - public Set fetchDocumentsForAllScopes( - String pluginIndex, - Set entities, - String RecipientType, - ResourceParser parser - ) { + public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String RecipientType) { // "*" must match all scopes - return fetchDocumentsForAGivenScope(pluginIndex, entities, RecipientType, "*", parser); + return fetchDocumentsForAGivenScope(pluginIndex, entities, RecipientType, "*"); } /** @@ -356,7 +352,7 @@ public Set fetchDocumentsForAllScopes( * "query": { * "bool": { * "must": [ - * { "term": { "source_idx": "system_index_name" } }, + * { "term": { "source_idx": "resource_index_name" } }, * { * "bool": { * "should": [ @@ -388,8 +384,7 @@ public Set fetchDocumentsForAllScopes( *
      21. "roles" - for role-based access
      22. *
      23. "backend_roles" - for backend role-based access
      24. * - * @param scope The scope of the access. Should be implementation of {@link org.opensearch.security.spi.resources.ResourceAccessScope} - * @param clazz Class to deserialize each document from Response into + * @param scope The scope of the access. Should be implementation of {@link ResourceAccessScope} * @return Set List of resource IDs that match the criteria. The list may be empty * if no matches are found * @@ -404,13 +399,7 @@ public Set fetchDocumentsForAllScopes( *
      25. Properly cleans up scroll context after use
      26. * */ - public Set fetchDocumentsForAGivenScope( - String pluginIndex, - Set entities, - String RecipientType, - String scope, - ResourceParser parser - ) { + public Set fetchDocumentsForAGivenScope(String pluginIndex, Set entities, String RecipientType, String scope) { LOGGER.debug( "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", pluginIndex, @@ -419,6 +408,9 @@ public Set fetchDocumentsForAGivenScope( entities ); + // To allow "public" resources to be matched for any user, role, backend_role + entities.add("*"); + Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); @@ -450,7 +442,7 @@ public Set fetchDocumentsForAGivenScope( LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); - return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, parser); + return resourceIds; } catch (Exception e) { LOGGER.error( @@ -499,7 +491,6 @@ public Set fetchDocumentsForAGivenScope( * @param pluginIndex The source index to match against the source_idx field * @param field The field name to search in. Must be a valid field in the index mapping * @param value The value to match for the specified field. Performs exact term matching - * @param clazz Class to deserialize each document from Response into * @return Set List of resource IDs that match the criteria. Returns an empty list * if no matches are found * @@ -520,7 +511,7 @@ public Set fetchDocumentsForAGivenScope( * Set resources = fetchDocumentsByField("myIndex", "status", "active"); * */ - public Set fetchDocumentsByField(String pluginIndex, String field, String value, ResourceParser parser) { + public Set fetchDocumentsByField(String pluginIndex, String field, String value) { if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { throw new IllegalArgumentException("pluginIndex, field, and value must not be null or empty"); } @@ -543,7 +534,7 @@ public Set fetchDocumentsByField(String pluginIndex, Str LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); - return resourceIds.isEmpty() ? Set.of() : getResourcesFromIds(resourceIds, pluginIndex, parser); + return resourceIds; } catch (Exception e) { LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); @@ -569,7 +560,7 @@ public Set fetchDocumentsByField(String pluginIndex, Str * "query": { * "bool": { * "must": [ - * { "term": { "source_idx": "system_index_name" } }, + * { "term": { "source_idx": "resource_index_name" } }, * { "term": { "resource_id": "resource_id_value" } } * ] * } @@ -723,7 +714,7 @@ public ResourceSharing updateResourceSharingInfo( XContentBuilder builder; Map shareWithMap; try { - builder = XContentFactory.jsonBuilder(); + builder = jsonBuilder(); shareWith.toXContent(builder, ToXContent.EMPTY_PARAMS); String json = builder.toString(); shareWithMap = DefaultObjectMapper.readValue(json, new TypeReference<>() { @@ -900,7 +891,7 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId *

        Example document structure: *

              * {
        -     *   "source_idx": "system_index_name",
        +     *   "source_idx": "resource_index_name",
              *   "resource_id": "resource_id",
              *   "share_with": {
              *     "scope": {
        @@ -1170,11 +1161,19 @@ public boolean deleteAllRecordsForUser(String name) {
              * Fetches all documents from the specified resource index and deserializes them into the specified class.
              *
              * @param resourceIndex The resource index to fetch documents from.
        -     * @param clazz The class to deserialize the documents into.
        +     * @param parser The class to deserialize the documents into a specified type defined by the parser.
              * @return A set of deserialized documents.
              */
        -    private  Set getResourcesFromIds(Set resourceIds, String resourceIndex, ResourceParser parser) {
        +    public  Set getResourceDocumentsFromIds(
        +        Set resourceIds,
        +        String resourceIndex,
        +        ResourceParser parser
        +    ) {
                 Set result = new HashSet<>();
        +        if (resourceIds.isEmpty()) {
        +            return result;
        +        }
        +
                 // stashing Context to avoid permission issues in-case resourceIndex is a system index
                 // TODO: Once stashContext is replaced with switchContext this call will have to be modified
                 try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) {
        diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java
        index f16887f12b..414e25e305 100644
        --- a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java
        +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java
        @@ -20,7 +20,7 @@
          */
         public class ListAccessibleResourcesRequest extends ActionRequest {
         
        -    private String resourceIndex;
        +    private final String resourceIndex;
         
             public ListAccessibleResourcesRequest(String resourceIndex) {
                 this.resourceIndex = resourceIndex;
        diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java
        index 1a678ac2ce..e242b9b353 100644
        --- a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java
        +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java
        @@ -9,7 +9,7 @@
         package org.opensearch.security.rest.resources.access.list;
         
         import java.io.IOException;
        -import java.util.HashSet;
        +import java.lang.reflect.InvocationTargetException;
         import java.util.Set;
         
         import org.opensearch.core.action.ActionResponse;
        @@ -24,19 +24,33 @@
          */
         public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject {
             private final Set resources;
        +    private final String resourceClass;
         
        -    public ListAccessibleResourcesResponse(Set resources) {
        +    public ListAccessibleResourcesResponse(String resourceClass, Set resources) {
        +        this.resourceClass = resourceClass;
                 this.resources = resources;
             }
         
             @Override
             public void writeTo(StreamOutput out) throws IOException {
        +        out.writeString(resourceClass);
                 out.writeCollection(resources);
             }
         
        -    public ListAccessibleResourcesResponse(StreamInput in) {
        -        // TODO need to fix this to return correct value
        -        this.resources = new HashSet<>();
        +    public ListAccessibleResourcesResponse(StreamInput in) throws IOException, ClassNotFoundException {
        +        this.resourceClass = in.readString();
        +
        +        // TODO check if there is a better way to handle this
        +        Class clazz = Class.forName(this.resourceClass);
        +        @SuppressWarnings("unchecked")
        +        Class resourceClass = (Class) clazz;
        +        this.resources = in.readSet(i -> {
        +            try {
        +                return resourceClass.getDeclaredConstructor(StreamInput.class).newInstance(i);
        +            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        +                throw new RuntimeException(e);
        +            }
        +        });
             }
         
             @Override
        diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java
        index ff07b1e455..1f1f189ee1 100644
        --- a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java
        +++ b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java
        @@ -17,7 +17,7 @@ public class VerifyResourceAccessAction extends ActionType {
        -    private static final Logger log = LogManager.getLogger(ListAccessibleResourcesTransportAction.class);
        +    private static final Logger log = LogManager.getLogger(TransportListAccessibleResourcesAction.class);
             private final ResourceAccessHandler resourceAccessHandler;
         
             @Inject
        -    public ListAccessibleResourcesTransportAction(
        +    public TransportListAccessibleResourcesAction(
                 TransportService transportService,
                 ActionFilters actionFilters,
                 ResourceAccessHandler resourceAccessHandler
        @@ -46,7 +47,8 @@ protected void doExecute(Task task, ListAccessibleResourcesRequest request, Acti
                 try {
                     Set resources = resourceAccessHandler.getAccessibleResourcesForCurrentUser(request.getResourceIndex());
                     log.info("Successfully fetched accessible resources for current user : {}", resources);
        -            listener.onResponse(new ListAccessibleResourcesResponse(resources));
        +            String resourceType = OpenSearchSecurityPlugin.getResourceProviders().get(request.getResourceIndex()).getResourceType();
        +            listener.onResponse(new ListAccessibleResourcesResponse(resourceType, resources));
                 } catch (Exception e) {
                     log.info("Failed to list accessible resources for current user: ", e);
                     listener.onFailure(e);
        diff --git a/src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java
        similarity index 92%
        rename from src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java
        rename to src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java
        index fd7324dca1..7a04e5d46f 100644
        --- a/src/main/java/org/opensearch/security/transport/resources/access/RevokeResourceAccessTransportAction.java
        +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java
        @@ -24,12 +24,12 @@
         import org.opensearch.tasks.Task;
         import org.opensearch.transport.TransportService;
         
        -public class RevokeResourceAccessTransportAction extends HandledTransportAction {
        -    private static final Logger log = LogManager.getLogger(RevokeResourceAccessTransportAction.class);
        +public class TransportRevokeResourceAccessAction extends HandledTransportAction {
        +    private static final Logger log = LogManager.getLogger(TransportRevokeResourceAccessAction.class);
             private final ResourceAccessHandler resourceAccessHandler;
         
             @Inject
        -    public RevokeResourceAccessTransportAction(
        +    public TransportRevokeResourceAccessAction(
                 TransportService transportService,
                 ActionFilters actionFilters,
                 ResourceAccessHandler resourceAccessHandler
        diff --git a/src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java
        similarity index 92%
        rename from src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java
        rename to src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java
        index 1d8111f1b6..4959de2ab2 100644
        --- a/src/main/java/org/opensearch/security/transport/resources/access/ShareResourceTransportAction.java
        +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java
        @@ -24,12 +24,12 @@
         import org.opensearch.tasks.Task;
         import org.opensearch.transport.TransportService;
         
        -public class ShareResourceTransportAction extends HandledTransportAction {
        -    private static final Logger log = LogManager.getLogger(ShareResourceTransportAction.class);
        +public class TransportShareResourceAction extends HandledTransportAction {
        +    private static final Logger log = LogManager.getLogger(TransportShareResourceAction.class);
             private final ResourceAccessHandler resourceAccessHandler;
         
             @Inject
        -    public ShareResourceTransportAction(
        +    public TransportShareResourceAction(
                 TransportService transportService,
                 ActionFilters actionFilters,
                 ResourceAccessHandler resourceAccessHandler
        diff --git a/src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java
        similarity index 92%
        rename from src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java
        rename to src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java
        index f608453c02..0b732a1cb1 100644
        --- a/src/main/java/org/opensearch/security/transport/resources/access/VerifyResourceAccessTransportAction.java
        +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java
        @@ -23,12 +23,12 @@
         import org.opensearch.tasks.Task;
         import org.opensearch.transport.TransportService;
         
        -public class VerifyResourceAccessTransportAction extends HandledTransportAction {
        -    private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class);
        +public class TransportVerifyResourceAccessAction extends HandledTransportAction {
        +    private static final Logger log = LogManager.getLogger(TransportVerifyResourceAccessAction.class);
             private final ResourceAccessHandler resourceAccessHandler;
         
             @Inject
        -    public VerifyResourceAccessTransportAction(
        +    public TransportVerifyResourceAccessAction(
                 TransportService transportService,
                 ActionFilters actionFilters,
                 Client nodeClient,
        
        From 31f3e8245c60f4c509fe6b8aed097100f2e9d462 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Wed, 8 Jan 2025 18:21:41 -0500
        Subject: [PATCH 068/122] Updates Resource to be an abstract class
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/spi/resources/Resource.java         | 16 +++++++++-------
         1 file changed, 9 insertions(+), 7 deletions(-)
        
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java b/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java
        index 9116ed0a9e..18de796c8e 100644
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java
        +++ b/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java
        @@ -17,15 +17,17 @@
         /**
          * Marker interface for all resources
          */
        -public interface Resource extends NamedWriteable, ToXContentFragment {
        +public abstract class Resource implements NamedWriteable, ToXContentFragment {
             /**
        -     * Get the resource name
        +     * Abstract method to get the resource name.
        +     * Must be implemented by subclasses.
        +     *
              * @return resource name
              */
        -    String getResourceName();
        +    public abstract String getResourceName();
         
        -    // For de-serialization
        -    Resource readFrom(StreamInput in) throws IOException;
        -
        -    // TODO: Next iteration, check if getResourceType() should be implemented
        +    /**
        +     * Enforces that all subclasses have a constructor accepting StreamInput.
        +     */
        +    protected Resource(StreamInput in) throws IOException {}
         }
        
        From b97e58cf2d72dcd56b018f25183b53bb2a9369e7 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Wed, 8 Jan 2025 18:37:44 -0500
        Subject: [PATCH 069/122] Adds sample plugin to demonstrate resource sharing
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../org/opensearch/sample/SampleResource.java | 12 ++--
         .../sample/SampleResourcePlugin.java          | 18 +++---
         .../verify/VerifyResourceAccessAction.java    | 25 --------
         .../verify/VerifyResourceAccessRequest.java   | 62 -------------------
         .../verify/VerifyResourceAccessResponse.java  | 52 ----------------
         .../VerifyResourceAccessRestAction.java       | 55 ----------------
         .../rest}/create/CreateResourceAction.java    |  2 +-
         .../rest}/create/CreateResourceRequest.java   |  2 +-
         .../rest}/create/CreateResourceResponse.java  |  2 +-
         .../create/CreateResourceRestAction.java      |  2 +-
         .../rest}/delete/DeleteResourceAction.java    |  2 +-
         .../rest}/delete/DeleteResourceRequest.java   |  2 +-
         .../rest}/delete/DeleteResourceResponse.java  |  2 +-
         .../delete/DeleteResourceRestAction.java      |  2 +-
         .../CreateResourceTransportAction.java        |  8 +--
         .../DeleteResourceTransportAction.java        |  8 +--
         .../VerifyResourceAccessTransportAction.java  | 61 ------------------
         .../opensearch/sample/utils/Validation.java   | 36 -----------
         18 files changed, 28 insertions(+), 325 deletions(-)
         delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java
         delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java
         delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java
         delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/create/CreateResourceAction.java (92%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/create/CreateResourceRequest.java (95%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/create/CreateResourceResponse.java (95%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/create/CreateResourceRestAction.java (97%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/delete/DeleteResourceAction.java (92%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/delete/DeleteResourceRequest.java (95%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/delete/DeleteResourceResponse.java (95%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{actions/resource => resource/actions/rest}/delete/DeleteResourceRestAction.java (96%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{transport/resource => resource/actions/transport}/CreateResourceTransportAction.java (91%)
         rename sample-resource-plugin/src/main/java/org/opensearch/sample/{transport/resource => resource/actions/transport}/DeleteResourceTransportAction.java (91%)
         delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java
         delete mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java
        
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java
        index a265f0cdaa..508d8e7597 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java
        @@ -19,15 +19,18 @@
         import org.opensearch.core.xcontent.XContentBuilder;
         import org.opensearch.security.spi.resources.Resource;
         
        -public class SampleResource implements Resource {
        +public class SampleResource extends Resource {
         
             private String name;
             private String description;
             private Map attributes;
         
        -    public SampleResource() {}
        +    public SampleResource() throws IOException {
        +        super(null);
        +    }
         
             public SampleResource(StreamInput in) throws IOException {
        +        super(in);
                 this.name = in.readString();
                 this.description = in.readString();
                 this.attributes = in.readMap(StreamInput::readString, StreamInput::readString);
        @@ -66,9 +69,4 @@ public void setAttributes(Map attributes) {
             public String getResourceName() {
                 return name;
             }
        -
        -    @Override
        -    public Resource readFrom(StreamInput streamInput) throws IOException {
        -        return new SampleResource(streamInput);
        -    }
         }
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java
        index 4c0ab20ffa..6c68ef81ab 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java
        @@ -42,15 +42,12 @@
         import org.opensearch.repositories.RepositoriesService;
         import org.opensearch.rest.RestController;
         import org.opensearch.rest.RestHandler;
        -import org.opensearch.sample.actions.access.verify.VerifyResourceAccessAction;
        -import org.opensearch.sample.actions.access.verify.VerifyResourceAccessRestAction;
        -import org.opensearch.sample.actions.resource.create.CreateResourceAction;
        -import org.opensearch.sample.actions.resource.create.CreateResourceRestAction;
        -import org.opensearch.sample.actions.resource.delete.DeleteResourceAction;
        -import org.opensearch.sample.actions.resource.delete.DeleteResourceRestAction;
        -import org.opensearch.sample.transport.access.VerifyResourceAccessTransportAction;
        -import org.opensearch.sample.transport.resource.CreateResourceTransportAction;
        -import org.opensearch.sample.transport.resource.DeleteResourceTransportAction;
        +import org.opensearch.sample.resource.actions.rest.create.CreateResourceAction;
        +import org.opensearch.sample.resource.actions.rest.create.CreateResourceRestAction;
        +import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceAction;
        +import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceRestAction;
        +import org.opensearch.sample.resource.actions.transport.CreateResourceTransportAction;
        +import org.opensearch.sample.resource.actions.transport.DeleteResourceTransportAction;
         import org.opensearch.script.ScriptService;
         import org.opensearch.security.spi.resources.ResourceParser;
         import org.opensearch.security.spi.resources.ResourceService;
        @@ -96,14 +93,13 @@ public List getRestHandlers(
                 IndexNameExpressionResolver indexNameExpressionResolver,
                 Supplier nodesInCluster
             ) {
        -        return List.of(new CreateResourceRestAction(), new VerifyResourceAccessRestAction(), new DeleteResourceRestAction());
        +        return List.of(new CreateResourceRestAction(), new DeleteResourceRestAction());
             }
         
             @Override
             public List> getActions() {
                 return List.of(
                     new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class),
        -            new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class),
                     new ActionHandler<>(DeleteResourceAction.INSTANCE, DeleteResourceTransportAction.class)
                 );
             }
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java
        deleted file mode 100644
        index 466cc901c6..0000000000
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessAction.java
        +++ /dev/null
        @@ -1,25 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.sample.actions.access.verify;
        -
        -import org.opensearch.action.ActionType;
        -
        -/**
        - * Action to verify resource access for current user
        - */
        -public class VerifyResourceAccessAction extends ActionType {
        -
        -    public static final VerifyResourceAccessAction INSTANCE = new VerifyResourceAccessAction();
        -
        -    public static final String NAME = "cluster:admin/sample-resource-plugin/verify/resource_access";
        -
        -    private VerifyResourceAccessAction() {
        -        super(NAME, VerifyResourceAccessResponse::new);
        -    }
        -}
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java
        deleted file mode 100644
        index b9ab4134c6..0000000000
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRequest.java
        +++ /dev/null
        @@ -1,62 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.sample.actions.access.verify;
        -
        -import java.io.IOException;
        -import java.util.Set;
        -
        -import org.opensearch.action.ActionRequest;
        -import org.opensearch.action.ActionRequestValidationException;
        -import org.opensearch.core.common.io.stream.StreamInput;
        -import org.opensearch.core.common.io.stream.StreamOutput;
        -import org.opensearch.sample.utils.Validation;
        -
        -public class VerifyResourceAccessRequest extends ActionRequest {
        -
        -    private final String resourceId;
        -
        -    private final String scope;
        -
        -    /**
        -     * Default constructor
        -     */
        -    public VerifyResourceAccessRequest(String resourceId, String scope) {
        -        this.resourceId = resourceId;
        -        this.scope = scope;
        -    }
        -
        -    /**
        -     * Constructor with stream input
        -     * @param in the stream input
        -     * @throws IOException IOException
        -     */
        -    public VerifyResourceAccessRequest(final StreamInput in) throws IOException {
        -        this.resourceId = in.readString();
        -        this.scope = in.readString();
        -    }
        -
        -    @Override
        -    public void writeTo(final StreamOutput out) throws IOException {
        -        out.writeString(resourceId);
        -        out.writeString(scope);
        -    }
        -
        -    @Override
        -    public ActionRequestValidationException validate() {
        -        return Validation.validateScopes(Set.of(scope));
        -    }
        -
        -    public String getResourceId() {
        -        return resourceId;
        -    }
        -
        -    public String getScope() {
        -        return scope;
        -    }
        -}
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java
        deleted file mode 100644
        index f7c419b9d1..0000000000
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessResponse.java
        +++ /dev/null
        @@ -1,52 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.sample.actions.access.verify;
        -
        -import java.io.IOException;
        -
        -import org.opensearch.core.action.ActionResponse;
        -import org.opensearch.core.common.io.stream.StreamInput;
        -import org.opensearch.core.common.io.stream.StreamOutput;
        -import org.opensearch.core.xcontent.ToXContentObject;
        -import org.opensearch.core.xcontent.XContentBuilder;
        -
        -public class VerifyResourceAccessResponse extends ActionResponse implements ToXContentObject {
        -    private final String message;
        -
        -    /**
        -     * Default constructor
        -     *
        -     * @param message The message
        -     */
        -    public VerifyResourceAccessResponse(String message) {
        -        this.message = message;
        -    }
        -
        -    @Override
        -    public void writeTo(StreamOutput out) throws IOException {
        -        out.writeString(message);
        -    }
        -
        -    /**
        -     * Constructor with StreamInput
        -     *
        -     * @param in the stream input
        -     */
        -    public VerifyResourceAccessResponse(final StreamInput in) throws IOException {
        -        message = in.readString();
        -    }
        -
        -    @Override
        -    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
        -        builder.startObject();
        -        builder.field("message", message);
        -        builder.endObject();
        -        return builder;
        -    }
        -}
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java
        deleted file mode 100644
        index 3118fd54e6..0000000000
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/access/verify/VerifyResourceAccessRestAction.java
        +++ /dev/null
        @@ -1,55 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.sample.actions.access.verify;
        -
        -import java.io.IOException;
        -import java.util.List;
        -import java.util.Map;
        -
        -import org.opensearch.client.node.NodeClient;
        -import org.opensearch.core.xcontent.XContentParser;
        -import org.opensearch.rest.BaseRestHandler;
        -import org.opensearch.rest.RestRequest;
        -import org.opensearch.rest.action.RestToXContentListener;
        -
        -import static java.util.Collections.singletonList;
        -import static org.opensearch.rest.RestRequest.Method.GET;
        -
        -public class VerifyResourceAccessRestAction extends BaseRestHandler {
        -
        -    public VerifyResourceAccessRestAction() {}
        -
        -    @Override
        -    public List routes() {
        -        return singletonList(new Route(GET, "/_plugins/sample_resource_sharing/verify_resource_access"));
        -    }
        -
        -    @Override
        -    public String getName() {
        -        return "verify_resource_access";
        -    }
        -
        -    @Override
        -    protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
        -        Map source;
        -        try (XContentParser parser = request.contentParser()) {
        -            source = parser.map();
        -        }
        -
        -        String resourceId = (String) source.get("resource_id");
        -        String scope = (String) source.get("scope");
        -
        -        final VerifyResourceAccessRequest verifyResourceAccessRequest = new VerifyResourceAccessRequest(resourceId, scope);
        -        return channel -> client.executeLocally(
        -            VerifyResourceAccessAction.INSTANCE,
        -            verifyResourceAccessRequest,
        -            new RestToXContentListener<>(channel)
        -        );
        -    }
        -}
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceAction.java
        similarity index 92%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceAction.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceAction.java
        index a2b91185e1..3e73b95f79 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceAction.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.create;
        +package org.opensearch.sample.resource.actions.rest.create;
         
         import org.opensearch.action.ActionType;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRequest.java
        similarity index 95%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRequest.java
        index fe579ff0d1..d3e9a7a468 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRequest.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRequest.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.create;
        +package org.opensearch.sample.resource.actions.rest.create;
         
         import java.io.IOException;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceResponse.java
        similarity index 95%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceResponse.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceResponse.java
        index 6b980c9912..33c8b0b1e6 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceResponse.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceResponse.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.create;
        +package org.opensearch.sample.resource.actions.rest.create;
         
         import java.io.IOException;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java
        similarity index 97%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java
        index f7aa1c76b5..bcfa0ae9df 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/create/CreateResourceRestAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.create;
        +package org.opensearch.sample.resource.actions.rest.create;
         
         import java.io.IOException;
         import java.util.List;
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceAction.java
        similarity index 92%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceAction.java
        index ccb31f7ab2..bfb672dfec 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceAction.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.delete;
        +package org.opensearch.sample.resource.actions.rest.delete;
         
         import org.opensearch.action.ActionType;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRequest.java
        similarity index 95%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRequest.java
        index 1cb58989d3..d7c4637f31 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRequest.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRequest.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.delete;
        +package org.opensearch.sample.resource.actions.rest.delete;
         
         import java.io.IOException;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceResponse.java
        similarity index 95%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceResponse.java
        index ba3cddc04b..31bf86ca79 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceResponse.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceResponse.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.delete;
        +package org.opensearch.sample.resource.actions.rest.delete;
         
         import java.io.IOException;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java
        similarity index 96%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java
        index 9a10ca2a62..6c88fdbc4d 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/actions/resource/delete/DeleteResourceRestAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.actions.resource.delete;
        +package org.opensearch.sample.resource.actions.rest.delete;
         
         import java.util.List;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java
        similarity index 91%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java
        index ad82e19576..c20f492985 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/CreateResourceTransportAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.transport.resource;
        +package org.opensearch.sample.resource.actions.transport;
         
         import java.io.IOException;
         
        @@ -23,9 +23,9 @@
         import org.opensearch.core.action.ActionListener;
         import org.opensearch.core.xcontent.ToXContent;
         import org.opensearch.core.xcontent.XContentBuilder;
        -import org.opensearch.sample.actions.resource.create.CreateResourceAction;
        -import org.opensearch.sample.actions.resource.create.CreateResourceRequest;
        -import org.opensearch.sample.actions.resource.create.CreateResourceResponse;
        +import org.opensearch.sample.resource.actions.rest.create.CreateResourceAction;
        +import org.opensearch.sample.resource.actions.rest.create.CreateResourceRequest;
        +import org.opensearch.sample.resource.actions.rest.create.CreateResourceResponse;
         import org.opensearch.security.spi.resources.Resource;
         import org.opensearch.tasks.Task;
         import org.opensearch.transport.TransportService;
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/DeleteResourceTransportAction.java
        similarity index 91%
        rename from sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java
        rename to sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/DeleteResourceTransportAction.java
        index bb403e3704..4ce8954bfe 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/resource/DeleteResourceTransportAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/DeleteResourceTransportAction.java
        @@ -6,7 +6,7 @@
          * compatible open source license.
          */
         
        -package org.opensearch.sample.transport.resource;
        +package org.opensearch.sample.resource.actions.transport;
         
         import org.apache.logging.log4j.LogManager;
         import org.apache.logging.log4j.Logger;
        @@ -22,9 +22,9 @@
         import org.opensearch.common.inject.Inject;
         import org.opensearch.common.util.concurrent.ThreadContext;
         import org.opensearch.core.action.ActionListener;
        -import org.opensearch.sample.actions.resource.delete.DeleteResourceAction;
        -import org.opensearch.sample.actions.resource.delete.DeleteResourceRequest;
        -import org.opensearch.sample.actions.resource.delete.DeleteResourceResponse;
        +import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceAction;
        +import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceRequest;
        +import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceResponse;
         import org.opensearch.tasks.Task;
         import org.opensearch.transport.TransportService;
         
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java
        deleted file mode 100644
        index 13954dbe2b..0000000000
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/transport/access/VerifyResourceAccessTransportAction.java
        +++ /dev/null
        @@ -1,61 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.sample.transport.access;
        -
        -import org.apache.logging.log4j.LogManager;
        -import org.apache.logging.log4j.Logger;
        -
        -import org.opensearch.action.support.ActionFilters;
        -import org.opensearch.action.support.HandledTransportAction;
        -import org.opensearch.client.Client;
        -import org.opensearch.common.inject.Inject;
        -import org.opensearch.core.action.ActionListener;
        -import org.opensearch.sample.SampleResourcePlugin;
        -import org.opensearch.sample.SampleResourceScope;
        -import org.opensearch.sample.actions.access.verify.VerifyResourceAccessAction;
        -import org.opensearch.sample.actions.access.verify.VerifyResourceAccessRequest;
        -import org.opensearch.sample.actions.access.verify.VerifyResourceAccessResponse;
        -import org.opensearch.security.spi.resources.ResourceService;
        -import org.opensearch.tasks.Task;
        -import org.opensearch.transport.TransportService;
        -
        -import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
        -
        -public class VerifyResourceAccessTransportAction extends HandledTransportAction {
        -    private static final Logger log = LogManager.getLogger(VerifyResourceAccessTransportAction.class);
        -
        -    @Inject
        -    public VerifyResourceAccessTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) {
        -        super(VerifyResourceAccessAction.NAME, transportService, actionFilters, VerifyResourceAccessRequest::new);
        -    }
        -
        -    @Override
        -    protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) {
        -        try {
        -            ResourceService rs = SampleResourcePlugin.GuiceHolder.getResourceService();
        -            boolean hasRequestedScopeAccess = rs.getResourceAccessControlPlugin()
        -                .hasPermission(request.getResourceId(), RESOURCE_INDEX_NAME, SampleResourceScope.valueOf(request.getScope()));
        -
        -            StringBuilder sb = new StringBuilder();
        -            sb.append("User ");
        -            sb.append(hasRequestedScopeAccess ? "has" : "does not have");
        -            sb.append(" requested scope ");
        -            sb.append(request.getScope());
        -            sb.append(" access to ");
        -            sb.append(request.getResourceId());
        -
        -            log.info(sb.toString());
        -            listener.onResponse(new VerifyResourceAccessResponse(sb.toString()));
        -        } catch (Exception e) {
        -            log.info("Failed to check user permissions for resource {}", request.getResourceId(), e);
        -            listener.onFailure(e);
        -        }
        -    }
        -
        -}
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java
        deleted file mode 100644
        index fac032402c..0000000000
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Validation.java
        +++ /dev/null
        @@ -1,36 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.sample.utils;
        -
        -import java.util.HashSet;
        -import java.util.Set;
        -
        -import org.opensearch.action.ActionRequestValidationException;
        -import org.opensearch.sample.SampleResourceScope;
        -import org.opensearch.security.spi.resources.ResourceAccessScope;
        -
        -public class Validation {
        -    public static ActionRequestValidationException validateScopes(Set scopes) {
        -        Set validScopes = new HashSet<>();
        -        for (SampleResourceScope scope : SampleResourceScope.values()) {
        -            validScopes.add(scope.name());
        -        }
        -        validScopes.add(ResourceAccessScope.READ_ONLY);
        -        validScopes.add(ResourceAccessScope.READ_WRITE);
        -
        -        for (String s : scopes) {
        -            if (!validScopes.contains(s)) {
        -                ActionRequestValidationException exception = new ActionRequestValidationException();
        -                exception.addValidationError("Invalid scope: " + s + ". Scope must be one of: " + validScopes);
        -                return exception;
        -            }
        -        }
        -        return null;
        -    }
        -}
        
        From bd148db7a42485ac68741895385c0f299b77eed7 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Wed, 8 Jan 2025 18:39:19 -0500
        Subject: [PATCH 070/122] Updates scope names
        
        Signed-off-by: Darshit Chanpura 
        ---
         build.gradle | 2 +-
         1 file changed, 1 insertion(+), 1 deletion(-)
        
        diff --git a/build.gradle b/build.gradle
        index 2124b0d9de..8e4148c23d 100644
        --- a/build.gradle
        +++ b/build.gradle
        @@ -574,7 +574,7 @@ tasks.integrationTest.finalizedBy(jacocoTestReport) // report is always generate
         check.dependsOn integrationTest
         
         dependencies {
        -    implementation project(path: ":opensearch-resource-sharing-spi")
        +    compileOnly project(path: ":opensearch-resource-sharing-spi")
             implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}"
             implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}"
             implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}"
        
        From fce2b9009a048f25c2d46905c5a60b0ebcd52bf2 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Thu, 9 Jan 2025 12:58:15 -0500
        Subject: [PATCH 071/122] Updates settings gradle to add spi
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../spi/resources/ResourceAccessScope.java    |  4 +--
         .../list/ListAccessibleResourcesResponse.java | 31 ++++++++++++-------
         .../security/util/ResourceValidation.java     |  4 +--
         .../security/resources/ShareWithTests.java    | 12 +++----
         4 files changed, 29 insertions(+), 22 deletions(-)
        
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java
        index b8dab4ff67..e6fd2a76f6 100644
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java
        +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java
        @@ -18,8 +18,8 @@
          * @opensearch.experimental
          */
         public interface ResourceAccessScope> {
        -    String READ_ONLY = "read_only";
        -    String READ_WRITE = "read_write";
        +    String RESTRICTED = "restricted";
        +    String PUBLIC = "public";
         
             static  & ResourceAccessScope> E fromValue(Class enumClass, String value) {
                 for (E enumConstant : enumClass.getEnumConstants()) {
        diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java
        index e242b9b353..8bb1f0ea02 100644
        --- a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java
        +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java
        @@ -37,20 +37,27 @@ public void writeTo(StreamOutput out) throws IOException {
                 out.writeCollection(resources);
             }
         
        -    public ListAccessibleResourcesResponse(StreamInput in) throws IOException, ClassNotFoundException {
        +    public ListAccessibleResourcesResponse(StreamInput in) throws IOException {
                 this.resourceClass = in.readString();
        +        this.resources = readResourcesFromStream(in);
        +    }
         
        -        // TODO check if there is a better way to handle this
        -        Class clazz = Class.forName(this.resourceClass);
        -        @SuppressWarnings("unchecked")
        -        Class resourceClass = (Class) clazz;
        -        this.resources = in.readSet(i -> {
        -            try {
        -                return resourceClass.getDeclaredConstructor(StreamInput.class).newInstance(i);
        -            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        -                throw new RuntimeException(e);
        -            }
        -        });
        +    private Set readResourcesFromStream(StreamInput in) {
        +        try {
        +            // TODO check if there is a better way to handle this
        +            Class clazz = Class.forName(this.resourceClass);
        +            @SuppressWarnings("unchecked")
        +            Class resourceClass = (Class) clazz;
        +            return in.readSet(i -> {
        +                try {
        +                    return resourceClass.getDeclaredConstructor(StreamInput.class).newInstance(i);
        +                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        +                    throw new RuntimeException(e);
        +                }
        +            });
        +        } catch (ClassNotFoundException | IOException e) {
        +            return Set.of();
        +        }
             }
         
             @Override
        diff --git a/src/main/java/org/opensearch/security/util/ResourceValidation.java b/src/main/java/org/opensearch/security/util/ResourceValidation.java
        index 3850087e4e..428aae2cf2 100644
        --- a/src/main/java/org/opensearch/security/util/ResourceValidation.java
        +++ b/src/main/java/org/opensearch/security/util/ResourceValidation.java
        @@ -17,8 +17,8 @@
         public class ResourceValidation {
             public static ActionRequestValidationException validateScopes(Set scopes) {
                 Set validScopes = new HashSet<>();
        -        validScopes.add(ResourceAccessScope.READ_ONLY);
        -        validScopes.add(ResourceAccessScope.READ_WRITE);
        +        validScopes.add(ResourceAccessScope.RESTRICTED);
        +        validScopes.add(ResourceAccessScope.PUBLIC);
         
                 // TODO See if we can add custom scopes as part of this validation routine
         
        diff --git a/src/test/java/org/opensearch/security/resources/ShareWithTests.java b/src/test/java/org/opensearch/security/resources/ShareWithTests.java
        index 7c7b634e86..43b2b6f502 100644
        --- a/src/test/java/org/opensearch/security/resources/ShareWithTests.java
        +++ b/src/test/java/org/opensearch/security/resources/ShareWithTests.java
        @@ -89,12 +89,12 @@ public void testFromXContentWithStartObject() throws IOException {
                 XContentParser parser;
                 try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
                     builder.startObject()
        -                .startObject(ResourceAccessScope.READ_ONLY)
        +                .startObject(ResourceAccessScope.RESTRICTED)
                         .array("users", "user1", "user2")
                         .array("roles", "role1")
                         .array("backend_roles", "backend_role1")
                         .endObject()
        -                .startObject(ResourceAccessScope.READ_WRITE)
        +                .startObject(ResourceAccessScope.PUBLIC)
                         .array("users", "user3")
                         .array("roles", "role2", "role3")
                         .array("backend_roles")
        @@ -115,7 +115,7 @@ public void testFromXContentWithStartObject() throws IOException {
                 for (SharedWithScope scope : scopes) {
                     SharedWithScope.ScopeRecipients perScope = scope.getSharedWithPerScope();
                     Map> recipients = perScope.getRecipients();
        -            if (scope.getScope().equals(ResourceAccessScope.READ_ONLY)) {
        +            if (scope.getScope().equals(ResourceAccessScope.RESTRICTED)) {
                         MatcherAssert.assertThat(
                             recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(),
                             is(2)
        @@ -128,7 +128,7 @@ public void testFromXContentWithStartObject() throws IOException {
                             recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(),
                             is(1)
                         );
        -            } else if (scope.getScope().equals(ResourceAccessScope.READ_WRITE)) {
        +            } else if (scope.getScope().equals(ResourceAccessScope.PUBLIC)) {
                         MatcherAssert.assertThat(
                             recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(),
                             is(1)
        @@ -229,8 +229,8 @@ public void test_writeSharedWithScopesToStream() throws IOException {
                 StreamOutput mockStreamOutput = Mockito.mock(StreamOutput.class);
         
                 Set sharedWithScopes = new HashSet<>();
        -        sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.READ_ONLY, new SharedWithScope.ScopeRecipients(Map.of())));
        -        sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.READ_WRITE, new SharedWithScope.ScopeRecipients(Map.of())));
        +        sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.RESTRICTED, new SharedWithScope.ScopeRecipients(Map.of())));
        +        sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.PUBLIC, new SharedWithScope.ScopeRecipients(Map.of())));
         
                 ShareWith shareWith = new ShareWith(sharedWithScopes);
         
        
        From 1aec47a3f451d84c66caaf9fe12a63e002f64f88 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Thu, 9 Jan 2025 22:08:49 -0500
        Subject: [PATCH 072/122] Updates DLS Search handler to filter out resources
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/OpenSearchSecurityPlugin.java    |  3 +-
         .../configuration/DlsFlsValveImpl.java        | 33 ++++++++++++++-----
         .../SecurityFlsDlsIndexSearcherWrapper.java   |  3 +-
         .../privileges/dlsfls/DlsRestriction.java     |  2 +-
         .../privileges/dlsfls/DocumentPrivileges.java |  6 ++--
         .../resources/ResourceAccessHandler.java      | 28 +++++++++++-----
         6 files changed, 51 insertions(+), 24 deletions(-)
        
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index a5f2bdfea4..bc8de0a6c3 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -1194,7 +1194,8 @@ public Collection createComponents(
                         resolver,
                         xContentRegistry,
                         threadPool,
        -                dlsFlsBaseContext
        +                dlsFlsBaseContext,
        +                resourceAccessHandler
                     );
                     cr.subscribeOnChange(configMap -> { ((DlsFlsValveImpl) dlsFlsValve).updateConfiguration(cr.getConfiguration(CType.ROLES)); });
                 }
        diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        index 498b908e5d..9169cad529 100644
        --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        @@ -17,6 +17,7 @@
         import java.util.Comparator;
         import java.util.List;
         import java.util.Objects;
        +import java.util.Set;
         import java.util.concurrent.atomic.AtomicReference;
         import java.util.function.Consumer;
         import java.util.stream.StreamSupport;
        @@ -70,13 +71,9 @@
         import org.opensearch.security.privileges.DocumentAllowList;
         import org.opensearch.security.privileges.PrivilegesEvaluationContext;
         import org.opensearch.security.privileges.PrivilegesEvaluationException;
        -import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext;
        -import org.opensearch.security.privileges.dlsfls.DlsFlsLegacyHeaders;
        -import org.opensearch.security.privileges.dlsfls.DlsFlsProcessedConfig;
        -import org.opensearch.security.privileges.dlsfls.DlsRestriction;
        -import org.opensearch.security.privileges.dlsfls.FieldMasking;
        -import org.opensearch.security.privileges.dlsfls.IndexToRuleMap;
        +import org.opensearch.security.privileges.dlsfls.*;
         import org.opensearch.security.resolver.IndexResolverReplacer;
        +import org.opensearch.security.resources.ResourceAccessHandler;
         import org.opensearch.security.securityconf.DynamicConfigFactory;
         import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
         import org.opensearch.security.securityconf.impl.v7.RoleV7;
        @@ -98,6 +95,7 @@ public class DlsFlsValveImpl implements DlsFlsRequestValve {
             private final AtomicReference dlsFlsProcessedConfig = new AtomicReference<>();
             private final FieldMasking.Config fieldMaskingConfig;
             private final Settings settings;
        +    private final ResourceAccessHandler resourceAccessHandler;
         
             public DlsFlsValveImpl(
                 Settings settings,
        @@ -106,7 +104,8 @@ public DlsFlsValveImpl(
                 IndexNameExpressionResolver resolver,
                 NamedXContentRegistry namedXContentRegistry,
                 ThreadPool threadPool,
        -        DlsFlsBaseContext dlsFlsBaseContext
        +        DlsFlsBaseContext dlsFlsBaseContext,
        +        ResourceAccessHandler resourceAccessHandler
             ) {
                 super();
                 this.nodeClient = nodeClient;
        @@ -118,6 +117,7 @@ public DlsFlsValveImpl(
                 this.fieldMaskingConfig = FieldMasking.Config.fromSettings(settings);
                 this.dlsFlsBaseContext = dlsFlsBaseContext;
                 this.settings = settings;
        +        this.resourceAccessHandler = resourceAccessHandler;
         
                 clusterService.addListener(event -> {
                     DlsFlsProcessedConfig config = dlsFlsProcessedConfig.get();
        @@ -349,6 +349,7 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo
                 try {
                     String index = searchContext.indexShard().indexSettings().getIndex().getName();
         
        +            assert !Strings.isNullOrEmpty(index);
                     if (log.isTraceEnabled()) {
                         log.trace("handleSearchContext(); index: {}", index);
                     }
        @@ -374,13 +375,27 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo
                     }
         
                     PrivilegesEvaluationContext privilegesEvaluationContext = this.dlsFlsBaseContext.getPrivilegesEvaluationContext();
        -            if (privilegesEvaluationContext == null) {
        +
        +            if (privilegesEvaluationContext == null || OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
                         return;
                     }
         
                     DlsFlsProcessedConfig config = this.dlsFlsProcessedConfig.get();
         
        -            DlsRestriction dlsRestriction = config.getDocumentPrivileges().getRestriction(privilegesEvaluationContext, index);
        +            DlsRestriction dlsRestriction;
        +
        +            Set resourceIds;
        +            if (OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
        +                resourceIds = this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(index);
        +                if (resourceIds.isEmpty()) {
        +                    return;
        +                }
        +                // Create a DLS restriction to filter search results with accessible resources only
        +                dlsRestriction = this.resourceAccessHandler.createResourceDLSRestriction(resourceIds, namedXContentRegistry);
        +
        +            } else {
        +                dlsRestriction = config.getDocumentPrivileges().getRestriction(privilegesEvaluationContext, index);
        +            }
         
                     if (log.isTraceEnabled()) {
                         log.trace("handleSearchContext(); index: {}; dlsRestriction: {}", index, dlsRestriction);
        diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        index bb06604829..3b90dbcd2a 100644
        --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        @@ -29,6 +29,7 @@
         import org.opensearch.cluster.metadata.IndexMetadata;
         import org.opensearch.cluster.service.ClusterService;
         import org.opensearch.common.settings.Settings;
        +import org.opensearch.core.common.Strings;
         import org.opensearch.core.index.shard.ShardId;
         import org.opensearch.index.IndexService;
         import org.opensearch.index.mapper.SeqNoFieldMapper;
        @@ -49,8 +50,6 @@
         import org.opensearch.security.resources.ResourceAccessHandler;
         import org.opensearch.security.support.ConfigConstants;
         
        -import joptsimple.internal.Strings;
        -
         public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapper {
         
             public final Logger log = LogManager.getLogger(this.getClass());
        diff --git a/src/main/java/org/opensearch/security/privileges/dlsfls/DlsRestriction.java b/src/main/java/org/opensearch/security/privileges/dlsfls/DlsRestriction.java
        index 242e0000a4..01fccb78e6 100644
        --- a/src/main/java/org/opensearch/security/privileges/dlsfls/DlsRestriction.java
        +++ b/src/main/java/org/opensearch/security/privileges/dlsfls/DlsRestriction.java
        @@ -53,7 +53,7 @@ public class DlsRestriction extends AbstractRuleBasedPrivileges.Rule {
         
             private final ImmutableList queries;
         
        -    DlsRestriction(List queries) {
        +    public DlsRestriction(List queries) {
                 this.queries = ImmutableList.copyOf(queries);
             }
         
        diff --git a/src/main/java/org/opensearch/security/privileges/dlsfls/DocumentPrivileges.java b/src/main/java/org/opensearch/security/privileges/dlsfls/DocumentPrivileges.java
        index 2afcdd4b82..40ebfd7282 100644
        --- a/src/main/java/org/opensearch/security/privileges/dlsfls/DocumentPrivileges.java
        +++ b/src/main/java/org/opensearch/security/privileges/dlsfls/DocumentPrivileges.java
        @@ -92,7 +92,7 @@ protected DlsRestriction compile(PrivilegesEvaluationContext context, Collection
             /**
              * The basic rules of DLS are queries. This class encapsulates single queries.
              */
        -    static abstract class DlsQuery {
        +    public static abstract class DlsQuery {
                 final String queryString;
         
                 DlsQuery(String queryString) {
        @@ -118,7 +118,7 @@ public boolean equals(Object obj) {
                     return Objects.equals(this.queryString, other.queryString);
                 }
         
        -        protected QueryBuilder parseQuery(String queryString, NamedXContentRegistry xContentRegistry)
        +        public static QueryBuilder parseQuery(String queryString, NamedXContentRegistry xContentRegistry)
                     throws PrivilegesConfigurationValidationException {
                     try {
                         XContentParser parser = JsonXContent.jsonXContent.createParser(
        @@ -193,7 +193,7 @@ public static class RenderedDlsQuery {
                 private final QueryBuilder queryBuilder;
                 private final String renderedSource;
         
        -        RenderedDlsQuery(QueryBuilder queryBuilder, String renderedSource) {
        +        public RenderedDlsQuery(QueryBuilder queryBuilder, String renderedSource) {
                     this.queryBuilder = queryBuilder;
                     this.renderedSource = renderedSource;
                 }
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        index 361342e611..1602133b46 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        @@ -12,22 +12,22 @@
         package org.opensearch.security.resources;
         
         import java.io.IOException;
        -import java.util.Collections;
        -import java.util.HashSet;
        -import java.util.Map;
        -import java.util.Set;
        +import java.util.*;
         
        +import com.fasterxml.jackson.core.JsonProcessingException;
         import org.apache.logging.log4j.LogManager;
         import org.apache.logging.log4j.Logger;
         import org.apache.lucene.search.Query;
         
         import org.opensearch.common.util.concurrent.ThreadContext;
        -import org.opensearch.index.query.BoolQueryBuilder;
        -import org.opensearch.index.query.ConstantScoreQueryBuilder;
        -import org.opensearch.index.query.QueryBuilders;
        -import org.opensearch.index.query.QueryShardContext;
        +import org.opensearch.core.xcontent.NamedXContentRegistry;
        +import org.opensearch.index.query.*;
        +import org.opensearch.security.DefaultObjectMapper;
         import org.opensearch.security.OpenSearchSecurityPlugin;
         import org.opensearch.security.configuration.AdminDNs;
        +import org.opensearch.security.privileges.PrivilegesConfigurationValidationException;
        +import org.opensearch.security.privileges.dlsfls.DlsRestriction;
        +import org.opensearch.security.privileges.dlsfls.DocumentPrivileges;
         import org.opensearch.security.spi.resources.Resource;
         import org.opensearch.security.spi.resources.ResourceParser;
         import org.opensearch.security.support.ConfigConstants;
        @@ -379,4 +379,16 @@ public Query createResourceDlsQuery(Set resourceIds, QueryShardContext q
                 ConstantScoreQueryBuilder builder = new ConstantScoreQueryBuilder(boolQueryBuilder);
                 return builder.toQuery(queryShardContext);
             }
        +
        +    public DlsRestriction createResourceDLSRestriction(Set resourceIds, NamedXContentRegistry xContentRegistry)
        +        throws JsonProcessingException, PrivilegesConfigurationValidationException {
        +        String jsonQuery = String.format(
        +            "{ \"bool\": { \"filter\": [ { \"terms\": { \"_id\": %s } } ] } }",
        +            DefaultObjectMapper.writeValueAsString(resourceIds, true)
        +        );
        +        QueryBuilder queryBuilder = DocumentPrivileges.DlsQuery.parseQuery(jsonQuery, xContentRegistry);
        +        DocumentPrivileges.RenderedDlsQuery renderedDlsQuery = new DocumentPrivileges.RenderedDlsQuery(queryBuilder, jsonQuery);
        +        List documentPrivileges = List.of(renderedDlsQuery);
        +        return new DlsRestriction(documentPrivileges);
        +    }
         }
        
        From 11d8d6d22bdd4f5009d47eede2def37f0653b196 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 10 Jan 2025 12:19:19 -0500
        Subject: [PATCH 073/122] Updates if clause to match correctly
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../org/opensearch/security/configuration/DlsFlsValveImpl.java  | 2 +-
         1 file changed, 1 insertion(+), 1 deletion(-)
        
        diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        index 9169cad529..f9897baae0 100644
        --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        @@ -376,7 +376,7 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo
         
                     PrivilegesEvaluationContext privilegesEvaluationContext = this.dlsFlsBaseContext.getPrivilegesEvaluationContext();
         
        -            if (privilegesEvaluationContext == null || OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
        +            if (privilegesEvaluationContext == null && !OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
                         return;
                     }
         
        
        From 07b26b0f76f10d5d31b3db7e7715f7a4ec910302 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 10 Jan 2025 13:18:41 -0500
        Subject: [PATCH 074/122] Updates resource tests
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../test/resources/security/esnode-key.pem    |  28 ------------------
         .../src/test/resources/security/esnode.pem    |  25 ----------------
         .../src/test/resources/security/kirk-key.pem  |  28 ------------------
         .../src/test/resources/security/kirk.pem      |  27 -----------------
         .../src/test/resources/security/root-ca.pem   |  28 ------------------
         .../src/test/resources/security/sample.pem    |  25 ----------------
         .../src/test/resources/security/test-kirk.jks | Bin 3766 -> 0 bytes
         7 files changed, 161 deletions(-)
         delete mode 100644 sample-resource-plugin/src/test/resources/security/esnode-key.pem
         delete mode 100644 sample-resource-plugin/src/test/resources/security/esnode.pem
         delete mode 100644 sample-resource-plugin/src/test/resources/security/kirk-key.pem
         delete mode 100644 sample-resource-plugin/src/test/resources/security/kirk.pem
         delete mode 100644 sample-resource-plugin/src/test/resources/security/root-ca.pem
         delete mode 100644 sample-resource-plugin/src/test/resources/security/sample.pem
         delete mode 100644 sample-resource-plugin/src/test/resources/security/test-kirk.jks
        
        diff --git a/sample-resource-plugin/src/test/resources/security/esnode-key.pem b/sample-resource-plugin/src/test/resources/security/esnode-key.pem
        deleted file mode 100644
        index e90562be43..0000000000
        --- a/sample-resource-plugin/src/test/resources/security/esnode-key.pem
        +++ /dev/null
        @@ -1,28 +0,0 @@
        ------BEGIN PRIVATE KEY-----
        -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCm93kXteDQHMAv
        -bUPNPW5pyRHKDD42XGWSgq0k1D29C/UdyL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0
        -o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0HGkn47XVu3EwbfrTENg3jFu+Oem6a/50
        -1SzITzJWtS0cn2dIFOBimTVpT/4Zv5qrXA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1
        -MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8ndibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b
        -6l+KLo3IKpfTbAIJXIO+M67FLtWKtttDao94B069skzKk6FPgW/OZh6PRCD0oxOa
        -vV+ld2SjAgMBAAECggEAQK1+uAOZeaSZggW2jQut+MaN4JHLi61RH2cFgU3COLgo
        -FIiNjFn8f2KKU3gpkt1It8PjlmprpYut4wHI7r6UQfuv7ZrmncRiPWHm9PB82+ZQ
        -5MXYqj4YUxoQJ62Cyz4sM6BobZDrjG6HHGTzuwiKvHHkbsEE9jQ4E5m7yfbVvM0O
        -zvwrSOM1tkZihKSTpR0j2+taji914tjBssbn12TMZQL5ItGnhR3luY8mEwT9MNkZ
        -xg0VcREoAH+pu9FE0vPUgLVzhJ3be7qZTTSRqv08bmW+y1plu80GbppePcgYhEow
        -dlW4l6XPJaHVSn1lSFHE6QAx6sqiAnBz0NoTPIaLyQKBgQDZqDOlhCRciMRicSXn
        -7yid9rhEmdMkySJHTVFOidFWwlBcp0fGxxn8UNSBcXdSy7GLlUtH41W9PWl8tp9U
        -hQiiXORxOJ7ZcB80uNKXF01hpPj2DpFPWyHFxpDkWiTAYpZl68rOlYujxZUjJIej
        -VvcykBC2BlEOG9uZv2kxcqLyJwKBgQDEYULTxaTuLIa17wU3nAhaainKB3vHxw9B
        -Ksy5p3ND43UNEKkQm7K/WENx0q47TA1mKD9i+BhaLod98mu0YZ+BCUNgWKcBHK8c
        -uXpauvM/pLhFLXZ2jvEJVpFY3J79FSRK8bwE9RgKfVKMMgEk4zOyZowS8WScOqiy
        -hnQn1vKTJQKBgElhYuAnl9a2qXcC7KOwRsJS3rcKIVxijzL4xzOyVShp5IwIPbOv
        -hnxBiBOH/JGmaNpFYBcBdvORE9JfA4KMQ2fx53agfzWRjoPI1/7mdUk5RFI4gRb/
        -A3jZRBoopgFSe6ArCbnyQxzYzToG48/Wzwp19ZxYrtUR4UyJct6f5n27AoGBAJDh
        -KIpQQDOvCdtjcbfrF4aM2DPCfaGPzENJriwxy6oEPzDaX8Bu/dqI5Ykt43i/zQrX
        -GpyLaHvv4+oZVTiI5UIvcVO9U8hQPyiz9f7F+fu0LHZs6f7hyhYXlbe3XFxeop3f
        -5dTKdWgXuTTRF2L9dABkA2deS9mutRKwezWBMQk5AoGBALPtX0FrT1zIosibmlud
        -tu49A/0KZu4PBjrFMYTSEWGNJez3Fb2VsJwylVl6HivwbP61FhlYfyksCzQQFU71
        -+x7Nmybp7PmpEBECr3deoZKQ/acNHn0iwb0It+YqV5+TquQebqgwK6WCLsMuiYKT
        -bg/ch9Rhxbq22yrVgWHh6epp
        ------END PRIVATE KEY-----
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/test/resources/security/esnode.pem b/sample-resource-plugin/src/test/resources/security/esnode.pem
        deleted file mode 100644
        index 44101f0b37..0000000000
        --- a/sample-resource-plugin/src/test/resources/security/esnode.pem
        +++ /dev/null
        @@ -1,25 +0,0 @@
        ------BEGIN CERTIFICATE-----
        -MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL
        -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt
        -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl
        -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v
        -dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT
        -AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl
        -MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
        -A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud
        -yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0
        -HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr
        -XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n
        -dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD
        -ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R
        -BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA
        -AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF
        -BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo
        -wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ
        -KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz
        -pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi
        -7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh
        -hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L
        -camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg
        -PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg=
        ------END CERTIFICATE-----
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/test/resources/security/kirk-key.pem b/sample-resource-plugin/src/test/resources/security/kirk-key.pem
        deleted file mode 100644
        index 1949c26139..0000000000
        --- a/sample-resource-plugin/src/test/resources/security/kirk-key.pem
        +++ /dev/null
        @@ -1,28 +0,0 @@
        ------BEGIN PRIVATE KEY-----
        -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCVXDgEJQorgfXp
        -gpY0TgF55bD2xuzxN5Dc9rDfgWxrsOvOloMpd7k6FR71bKWjJi1KptSmM/cDElky
        -AWYKSfYWGiGxsQ+EQW+6kwCfEOHXQldn+0+JcWqP+osSPjtJfwRvRN5kRqP69MPo
        -7U0N2kdqenqMWjmG1chDGLRSOEGU5HIBiDxsZtOcvMaJ8b1eaW0lvS+6gFQ80AvB
        -GBkDDCOHHLtDXBylrZk2CQP8AzxNicIZ4B8G3CG3OHA8+nBtEtxZoIihrrkqlMt+
        -b/5N8u8zB0Encew0kdrc4R/2wS//ahr6U+9Siq8T7WsUtGwKj3BJClg6OyDJRhlu
        -y2gFnxoPAgMBAAECggEAP5TOycDkx+megAWVoHV2fmgvgZXkBrlzQwUG/VZQi7V4
        -ZGzBMBVltdqI38wc5MtbK3TCgHANnnKgor9iq02Z4wXDwytPIiti/ycV9CDRKvv0
        -TnD2hllQFjN/IUh5n4thHWbRTxmdM7cfcNgX3aZGkYbLBVVhOMtn4VwyYu/Mxy8j
        -xClZT2xKOHkxqwmWPmdDTbAeZIbSv7RkIGfrKuQyUGUaWhrPslvYzFkYZ0umaDgQ
        -OAthZew5Bz3OfUGOMPLH61SVPuJZh9zN1hTWOvT65WFWfsPd2yStI+WD/5PU1Doo
        -1RyeHJO7s3ug8JPbtNJmaJwHe9nXBb/HXFdqb976yQKBgQDNYhpu+MYSYupaYqjs
        -9YFmHQNKpNZqgZ4ceRFZ6cMJoqpI5dpEMqToFH7tpor72Lturct2U9nc2WR0HeEs
        -/6tiptyMPTFEiMFb1opQlXF2ae7LeJllntDGN0Q6vxKnQV+7VMcXA0Y8F7tvGDy3
        -qJu5lfvB1mNM2I6y/eMxjBuQhwKBgQC6K41DXMFro0UnoO879pOQYMydCErJRmjG
        -/tZSy3Wj4KA/QJsDSViwGfvdPuHZRaG9WtxdL6kn0w1exM9Rb0bBKl36lvi7o7xv
        -M+Lw9eyXMkww8/F5d7YYH77gIhGo+RITkKI3+5BxeBaUnrGvmHrpmpgRXWmINqr0
        -0jsnN3u0OQKBgCf45vIgItSjQb8zonLz2SpZjTFy4XQ7I92gxnq8X0Q5z3B+o7tQ
        -K/4rNwTju/sGFHyXAJlX+nfcK4vZ4OBUJjP+C8CTjEotX4yTNbo3S6zjMyGQqDI5
        -9aIOUY4pb+TzeUFJX7If5gR+DfGyQubvvtcg1K3GHu9u2l8FwLj87sRzAoGAflQF
        -RHuRiG+/AngTPnZAhc0Zq0kwLkpH2Rid6IrFZhGLy8AUL/O6aa0IGoaMDLpSWUJp
        -nBY2S57MSM11/MVslrEgGmYNnI4r1K25xlaqV6K6ztEJv6n69327MS4NG8L/gCU5
        -3pEm38hkUi8pVYU7in7rx4TCkrq94OkzWJYurAkCgYATQCL/rJLQAlJIGulp8s6h
        -mQGwy8vIqMjAdHGLrCS35sVYBXG13knS52LJHvbVee39AbD5/LlWvjJGlQMzCLrw
        -F7oILW5kXxhb8S73GWcuMbuQMFVHFONbZAZgn+C9FW4l7XyRdkrbR1MRZ2km8YMs
        -/AHmo368d4PSNRMMzLHw8Q==
        ------END PRIVATE KEY-----
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/test/resources/security/kirk.pem b/sample-resource-plugin/src/test/resources/security/kirk.pem
        deleted file mode 100644
        index 36b7e19a75..0000000000
        --- a/sample-resource-plugin/src/test/resources/security/kirk.pem
        +++ /dev/null
        @@ -1,27 +0,0 @@
        ------BEGIN CERTIFICATE-----
        -MIIEmDCCA4CgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLcwDQYJKoZIhvcNAQEL
        -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt
        -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl
        -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v
        -dCBDQTAeFw0yNDAyMjAxNzA0MjRaFw0zNDAyMTcxNzA0MjRaME0xCzAJBgNVBAYT
        -AmRlMQ0wCwYDVQQHDAR0ZXN0MQ8wDQYDVQQKDAZjbGllbnQxDzANBgNVBAsMBmNs
        -aWVudDENMAsGA1UEAwwEa2lyazCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
        -ggEBAJVcOAQlCiuB9emCljROAXnlsPbG7PE3kNz2sN+BbGuw686Wgyl3uToVHvVs
        -paMmLUqm1KYz9wMSWTIBZgpJ9hYaIbGxD4RBb7qTAJ8Q4ddCV2f7T4lxao/6ixI+
        -O0l/BG9E3mRGo/r0w+jtTQ3aR2p6eoxaOYbVyEMYtFI4QZTkcgGIPGxm05y8xonx
        -vV5pbSW9L7qAVDzQC8EYGQMMI4ccu0NcHKWtmTYJA/wDPE2JwhngHwbcIbc4cDz6
        -cG0S3FmgiKGuuSqUy35v/k3y7zMHQSdx7DSR2tzhH/bBL/9qGvpT71KKrxPtaxS0
        -bAqPcEkKWDo7IMlGGW7LaAWfGg8CAwEAAaOCASswggEnMAwGA1UdEwEB/wQCMAAw
        -DgYDVR0PAQH/BAQDAgXgMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMB0GA1UdDgQW
        -BBSjMS8tgguX/V7KSGLoGg7K6XMzIDCBzwYDVR0jBIHHMIHEgBQXh9+gWutmEqfV
        -0Pi6EkU8tysAnKGBlaSBkjCBjzETMBEGCgmSJomT8ixkARkWA2NvbTEXMBUGCgmS
        -JomT8ixkARkWB2V4YW1wbGUxGTAXBgNVBAoMEEV4YW1wbGUgQ29tIEluYy4xITAf
        -BgNVBAsMGEV4YW1wbGUgQ29tIEluYy4gUm9vdCBDQTEhMB8GA1UEAwwYRXhhbXBs
        -ZSBDb20gSW5jLiBSb290IENBghQNZAmZZn3EFOxBR4630XlhI+mo4jANBgkqhkiG
        -9w0BAQsFAAOCAQEACEUPPE66/Ot3vZqRGpjDjPHAdtOq+ebaglQhvYcnDw8LOZm8
        -Gbh9M88CiO6UxC8ipQLTPh2yyeWArkpJzJK/Pi1eoF1XLiAa0sQ/RaJfQWPm9dvl
        -1ZQeK5vfD4147b3iBobwEV+CR04SKow0YeEEzAJvzr8YdKI6jqr+2GjjVqzxvRBy
        -KRVHWCFiR7bZhHGLq3br8hSu0hwjb3oGa1ZI8dui6ujyZt6nm6BoEkau3G/6+zq9
        -E6vX3+8Fj4HKCAL6i0SwfGmEpTNp5WUhqibK/fMhhmMT4Mx6MxkT+OFnIjdUU0S/
        -e3kgnG8qjficUr38CyEli1U0M7koIXUZI7r+LQ==
        ------END CERTIFICATE-----
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/test/resources/security/root-ca.pem b/sample-resource-plugin/src/test/resources/security/root-ca.pem
        deleted file mode 100644
        index d33f5f7216..0000000000
        --- a/sample-resource-plugin/src/test/resources/security/root-ca.pem
        +++ /dev/null
        @@ -1,28 +0,0 @@
        ------BEGIN CERTIFICATE-----
        -MIIExjCCA66gAwIBAgIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcNAQEL
        -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt
        -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl
        -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v
        -dCBDQTAeFw0yNDAyMjAxNzAwMzZaFw0zNDAyMTcxNzAwMzZaMIGPMRMwEQYKCZIm
        -iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ
        -RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290
        -IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG
        -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEPyN7J9VGPyJcQmCBl5TGwfSzvVdWwoQU
        -j9aEsdfFJ6pBCDQSsj8Lv4RqL0dZra7h7SpZLLX/YZcnjikrYC+rP5OwsI9xEE/4
        -U98CsTBPhIMgqFK6SzNE5494BsAk4cL72dOOc8tX19oDS/PvBULbNkthQ0aAF1dg
        -vbrHvu7hq7LisB5ZRGHVE1k/AbCs2PaaKkn2jCw/b+U0Ml9qPuuEgz2mAqJDGYoA
        -WSR4YXrOcrmPuRqbws464YZbJW898/0Pn/U300ed+4YHiNYLLJp51AMkR4YEw969
        -VRPbWIvLrd0PQBooC/eLrL6rvud/GpYhdQEUx8qcNCKd4bz3OaQ5AgMBAAGjggEW
        -MIIBEjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQU
        -F4ffoFrrZhKn1dD4uhJFPLcrAJwwgc8GA1UdIwSBxzCBxIAUF4ffoFrrZhKn1dD4
        -uhJFPLcrAJyhgZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJ
        -k/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYD
        -VQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUg
        -Q29tIEluYy4gUm9vdCBDQYIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcN
        -AQELBQADggEBAL3Q3AHUhMiLUy6OlLSt8wX9I2oNGDKbBu0atpUNDztk/0s3YLQC
        -YuXgN4KrIcMXQIuAXCx407c+pIlT/T1FNn+VQXwi56PYzxQKtlpoKUL3oPQE1d0V
        -6EoiNk+6UodvyZqpdQu7fXVentRMk1QX7D9otmiiNuX+GSxJhJC2Lyzw65O9EUgG
        -1yVJon6RkUGtqBqKIuLksKwEr//ELnjmXit4LQKSnqKr0FTCB7seIrKJNyb35Qnq
        -qy9a/Unhokrmdda1tr6MbqU8l7HmxLuSd/Ky+L0eDNtYv6YfMewtjg0TtAnFyQov
        -rdXmeq1dy9HLo3Ds4AFz3Gx9076TxcRS/iI=
        ------END CERTIFICATE-----
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/test/resources/security/sample.pem b/sample-resource-plugin/src/test/resources/security/sample.pem
        deleted file mode 100644
        index 44101f0b37..0000000000
        --- a/sample-resource-plugin/src/test/resources/security/sample.pem
        +++ /dev/null
        @@ -1,25 +0,0 @@
        ------BEGIN CERTIFICATE-----
        -MIIEPDCCAySgAwIBAgIUaYSlET3nzsotWTrWueVPPh10yLYwDQYJKoZIhvcNAQEL
        -BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt
        -cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl
        -IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v
        -dCBDQTAeFw0yNDAyMjAxNzAzMjVaFw0zNDAyMTcxNzAzMjVaMFcxCzAJBgNVBAYT
        -AmRlMQ0wCwYDVQQHDAR0ZXN0MQ0wCwYDVQQKDARub2RlMQ0wCwYDVQQLDARub2Rl
        -MRswGQYDVQQDDBJub2RlLTAuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
        -A4IBDwAwggEKAoIBAQCm93kXteDQHMAvbUPNPW5pyRHKDD42XGWSgq0k1D29C/Ud
        -yL21HLzTJa49ZU2ldIkSKs9JqbkHdyK0o8MO6L8dotLoYbxDWbJFW8bp1w6tDTU0
        -HGkn47XVu3EwbfrTENg3jFu+Oem6a/501SzITzJWtS0cn2dIFOBimTVpT/4Zv5qr
        -XA6Cp4biOmoTYWhi/qQl8d0IaADiqoZ1MvZbZ6x76qTrRAbg+UWkpTEXoH1xTc8n
        -dibR7+HP6OTqCKvo1NhE8uP4pY+fWd6b6l+KLo3IKpfTbAIJXIO+M67FLtWKtttD
        -ao94B069skzKk6FPgW/OZh6PRCD0oxOavV+ld2SjAgMBAAGjgcYwgcMwRwYDVR0R
        -BEAwPogFKgMEBQWCEm5vZGUtMC5leGFtcGxlLmNvbYIJbG9jYWxob3N0hxAAAAAA
        -AAAAAAAAAAAAAAABhwR/AAABMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEF
        -BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU0/qDQaY10jIo
        -wCjLUpz/HfQXyt8wHwYDVR0jBBgwFoAUF4ffoFrrZhKn1dD4uhJFPLcrAJwwDQYJ
        -KoZIhvcNAQELBQADggEBAGbij5WyF0dKhQodQfTiFDb73ygU6IyeJkFSnxF67gDz
        -pQJZKFvXuVBa3cGP5e7Qp3TK50N+blXGH0xXeIV9lXeYUk4hVfBlp9LclZGX8tGi
        -7Xa2enMvIt5q/Yg3Hh755ZxnDYxCoGkNOXUmnMusKstE0YzvZ5Gv6fcRKFBUgZLh
        -hUBqIEAYly1EqH/y45APiRt3Nor1yF6zEI4TnL0yNrHw6LyQkUNCHIGMJLfnJQ9L
        -camMGIXOx60kXNMTigF9oXXwixWAnDM9y3QT8QXA7hej/4zkbO+vIeV/7lGUdkyg
        -PAi92EvyxmsliEMyMR0VINl8emyobvfwa7oMeWMR+hg=
        ------END CERTIFICATE-----
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/test/resources/security/test-kirk.jks b/sample-resource-plugin/src/test/resources/security/test-kirk.jks
        deleted file mode 100644
        index 6c8c5ef77e20980f8c78295b159256b805da6a28..0000000000000000000000000000000000000000
        GIT binary patch
        literal 0
        HcmV?d00001
        
        literal 3766
        zcmd^=c{r47AIImJ%`(PV###wuU&o%k$xbMgr4m`Pk2Tv-j4?=zEwY?!X|aVw)I`=A
        zPAY52Rt6yODkPjhAQ%WsfbL*f;mp!-018Nf*#Q6sf)b!}Nv;s_8gzOC@mTmi+D9F}jyYkhL=#Xk3eYM2csmxKA&W!xAdE{tZ2mEGS
        z;L%QU`DHcrbdbw$3GsKUvmfQu0Z^?sH7B)!W)eLbG*fXB^G$&6CbCnj4~
        z*J>Rkut6vL1EvT!JqAq#X=O~#!JHQ#QVSPuOGlnLrXXB~{{FsGRq?o?I;>^GFEhMB
        zw;z!v1sXap8nq3zz&+prKs-DRPm*XsS4BaP6Z{8tM~n@m|rxMA=p6*i(w=7
        z*2&*Yg-uWU$5|W>>g5h)Fn{3B={`skAJ5_wXB5pDwyj{vG1_{{Y-`wB_i^B!5PA|=
        zrx=_>rprb&75BQ=J)SKPAJI;?(D#46)o+a?SsR^-&qJjXY2ER8S*1ZvU`t7~M6?NKULuzlAZ8C#X9>8j2;WDY
        z(TY-^!`&0%67`u|U_-Y(knWVcSlh-kwZQ6KG@S?L`W!iVl>Gyd(LnpMc@C!QeY{(E
        z)uAwF_CcqH#00}jer2dQk3}R|p^87XCxR8`n4c@g9rASTt9$8}SuGW!!+QQ&w&G!P
        zvv5Mft<&pzv^&XuuQAj&ieoa*3nI-hx}0`4kym=(cd>?v6yM3v43y@5@;yPeJ_N{@
        z622W$@5Z4VqliMF3GAf_RcB;$HX^%cwTCgxg^4)5I0?*&oW|giBB@nUNBO+IX=iON
        zo~;L}HOwhyeqH4GHvAQ5i=|0c+_5*661aDyT_tr=I#+Zog%!9nRiuBb8m&SS4qp2fv7HJMG
        zwJFuqV*Hoq3`|Mayml;So|9W4Um6Lu8(k+(Hc2}p@&>?!7!7H~9*O%@BrKNAOa-~e
        z$e6#G)fJ+wNz5x9zU;#>&V}d
        z?!F1W_eNN;&LI9$!kWa0Zqa)0CVM4D=x(r>aXgW=XQ)PTRsJJ&MC?WjjoMwLRh`-I
        z8yD|^&(r#NU|pRpRF%wn&t%X`)8HQe%uxEKnXxIu9yui1s$eH0*YZ^Wvt25yOg6{5
        zPefKstjqam-PRDz=&-BVb^xZe>{C{$cza!_sV&3M*l0ocMJVr!l~TlJi4JChDn9Nn
        zc&la1caY}0P&Ho=r;)l;mKBf$V<6A*R6XC}s98g%I7ZIAFI=e6SqQ4;oevw)nw0%^
        zKq9#$;{3R0zJv}#mr7@}e+5-(`{C?^vEE#xb7uBY=X#_1v+@~@l?W@Zaq+Yo9bpu&
        zR<0us_T`(Q6qp1xYb)Rq;tJ|aTZ&y5xqx<_j-|>1$SEi@3!A||
        z9YH<3ub_#ai=2WG_V9iQ!NU8mB|$4ZK3Gr>_s15;6W-XV-*##3TjwoMP&yb
        zq!L{!sQoUn<_ZWb)BbzloM2Zs1tb=+FBn*$!EQmp3Ml#oe;g0);^XP&_osni`NR1A
        z0SL>FG{F)8;h%d#4-g0eK+%&0UD-=ghUr~yDQ?!lNE5tKiJ_rjY{@`Q1vjbVAFU;|?Qs;w|1hFx_
        z`*jR7rVAU>9*yRSpD1)#aOb!)@ak(5hk;guG$_9)=K8Ie^uOP<63|FjrX2UEcJw07
        zD5c?bxHD${?)1+CMgPg@0|kH>4NzJZO*;#rl-xA_8*SHCS}ygKZP7*uHbRtmaTE%n
        zp7Vt7QIt|IIN?)fyS#8IxKHO$?TeY{DpQl5^kyAd$HH^Aa)SJC+I0!ULR
        znF7*z6R6~{CCW6M^qKuU!N`I`>YB3i6toA7f7#3%T&$5&wm0nY{&d9(g)LB$%g9dX
        zf>HfjVn9;)rG-^=)tiGDd<5M4wDHPl@yEGU_whSh78l$%S*WCqjvj^Xt?_VKp0T{pQGU!F;?_^4EMT$__$E
        zH0hMGQlo@W2p^_tPZsnirl@pGb<#0a^*g5ihYtSzKKx%Wg;i4h8B_c6Z+PPWM!I%g
        zOr-dLp|0@RV@@&InVrwRJfPT~ZY840gT$Jl4)HP^qcTUWE~1&}C2wS3Sv9pJWiRva
        zyK}a9ilnrYe7SB$bu~GF&GM`D1h@ukNsJY|Yt>|?q(4gzgSUuGwSIfsmlD)%J2V0@
        zTU&-58&x%P)-#Oev2~&}bv^wwRbD$?Enu(jJiuwM3shGOZ{$juY+RGk#m^`!p7+vO
        zAjWFn1{dq`T?N^TggHmN3~VGf^5?a_)R-cj5yfk-?V<|S)%uKn{YGL)7(~eAhWA56
        zj7ZS7amp#qQM;t>%6F)v{1S-Gq>88IPiL?2X9=q_r$vhc4{Pd3$WssBMbZaV2W
        zu&8||{U99-3!x+JudoA1KSAx^0qg$*YLr)FKtJ($lC@k)W?khPY!~B&3F~Xnxs_WH)b*(MC{~@>r={U4@A6+2p8il>0lojdT`r8~C>rA6;jw^lZK9gk<_y!v
        za(Rbclc{1;TFBtT`lr|YO0}|UXzh>FLsx6RQUq8=?V4{NR#=oxL2}kHb-ZAfuN
        Date: Fri, 10 Jan 2025 13:34:07 -0500
        Subject: [PATCH 075/122] Changes SPI dependency visibility
        
        Signed-off-by: Darshit Chanpura 
        ---
         build.gradle | 2 +-
         1 file changed, 1 insertion(+), 1 deletion(-)
        
        diff --git a/build.gradle b/build.gradle
        index 8e4148c23d..2124b0d9de 100644
        --- a/build.gradle
        +++ b/build.gradle
        @@ -574,7 +574,7 @@ tasks.integrationTest.finalizedBy(jacocoTestReport) // report is always generate
         check.dependsOn integrationTest
         
         dependencies {
        -    compileOnly project(path: ":opensearch-resource-sharing-spi")
        +    implementation project(path: ":opensearch-resource-sharing-spi")
             implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}"
             implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}"
             implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}"
        
        From 289659fcb4d7c3310c811db45415e954ef5c4087 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 10 Jan 2025 14:03:20 -0500
        Subject: [PATCH 076/122] Fixes CI errors
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/OpenSearchSecurityPlugin.java    |  1 +
         .../configuration/DlsFlsValveImpl.java        |  7 ++++++-
         .../resources/ResourceAccessHandler.java      | 21 ++++++++++++++++---
         3 files changed, 25 insertions(+), 4 deletions(-)
        
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index bc8de0a6c3..211adc9319 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -1284,6 +1284,7 @@ public Collection createComponents(
                 components.add(dcf);
                 components.add(userService);
                 components.add(passwordHasher);
        +        components.add(resourceAccessHandler);
         
                 components.add(sslSettingsManager);
                 if (isSslCertReloadEnabled(settings) && sslCertificatesHotReloadEnabled(settings)) {
        diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        index f9897baae0..c603f4c02f 100644
        --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        @@ -71,7 +71,12 @@
         import org.opensearch.security.privileges.DocumentAllowList;
         import org.opensearch.security.privileges.PrivilegesEvaluationContext;
         import org.opensearch.security.privileges.PrivilegesEvaluationException;
        -import org.opensearch.security.privileges.dlsfls.*;
        +import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext;
        +import org.opensearch.security.privileges.dlsfls.DlsFlsLegacyHeaders;
        +import org.opensearch.security.privileges.dlsfls.DlsFlsProcessedConfig;
        +import org.opensearch.security.privileges.dlsfls.DlsRestriction;
        +import org.opensearch.security.privileges.dlsfls.FieldMasking;
        +import org.opensearch.security.privileges.dlsfls.IndexToRuleMap;
         import org.opensearch.security.resolver.IndexResolverReplacer;
         import org.opensearch.security.resources.ResourceAccessHandler;
         import org.opensearch.security.securityconf.DynamicConfigFactory;
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        index 03e1cfc13e..38b689bfad 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        @@ -12,7 +12,11 @@
         package org.opensearch.security.resources;
         
         import java.io.IOException;
        -import java.util.*;
        +import java.util.Collections;
        +import java.util.HashSet;
        +import java.util.List;
        +import java.util.Map;
        +import java.util.Set;
         
         import com.fasterxml.jackson.core.JsonProcessingException;
         import org.apache.logging.log4j.LogManager;
        @@ -21,7 +25,11 @@
         
         import org.opensearch.common.util.concurrent.ThreadContext;
         import org.opensearch.core.xcontent.NamedXContentRegistry;
        -import org.opensearch.index.query.*;
        +import org.opensearch.index.query.BoolQueryBuilder;
        +import org.opensearch.index.query.ConstantScoreQueryBuilder;
        +import org.opensearch.index.query.QueryBuilder;
        +import org.opensearch.index.query.QueryBuilders;
        +import org.opensearch.index.query.QueryShardContext;
         import org.opensearch.security.DefaultObjectMapper;
         import org.opensearch.security.OpenSearchSecurityPlugin;
         import org.opensearch.security.configuration.AdminDNs;
        @@ -51,7 +59,6 @@ public ResourceAccessHandler(
                 final ResourceSharingIndexHandler resourceSharingIndexHandler,
                 AdminDNs adminDns
             ) {
        -        super();
                 this.threadContext = threadPool.getThreadContext();
                 this.resourceSharingIndexHandler = resourceSharingIndexHandler;
                 this.adminDNs = adminDns;
        @@ -380,6 +387,14 @@ public Query createResourceDLSQuery(Set resourceIds, QueryShardContext q
                 return builder.toQuery(queryShardContext);
             }
         
        +    /**
        +     * Creates a DLS restriction for the given resource IDs.
        +     * @param resourceIds The resource IDs to create the restriction for.
        +     * @param xContentRegistry The named XContent registry.
        +     * @return The DLS restriction.
        +     * @throws JsonProcessingException If an error occurs while processing JSON.
        +     * @throws PrivilegesConfigurationValidationException If the privileges configuration is invalid.
        +     */
             public DlsRestriction createResourceDLSRestriction(Set resourceIds, NamedXContentRegistry xContentRegistry)
                 throws JsonProcessingException, PrivilegesConfigurationValidationException {
                 String jsonQuery = String.format(
        
        From 512c1a867fe93ea644fb66ed0243733f76312b69 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 10 Jan 2025 14:44:06 -0500
        Subject: [PATCH 077/122] Fixes tests running in SSL only mode
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../org/opensearch/security/OpenSearchSecurityPlugin.java  | 7 +++++--
         1 file changed, 5 insertions(+), 2 deletions(-)
        
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index 211adc9319..68a1399cad 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -2163,8 +2163,11 @@ public void onNodeStarted(DiscoveryNode localNode) {
                     cr.initOnNodeStart();
                 }
         
        -        // create resource sharing index if absent
        -        rmr.createResourceSharingIndexIfAbsent();
        +        // rmr will be null when sec plugin is disabled or is in SSLOnly mode, hence rmr will not be instantiated
        +        if (rmr != null) {
        +            // create resource sharing index if absent
        +            rmr.createResourceSharingIndexIfAbsent();
        +        }
         
                 final Set securityModules = ReflectionHelper.getModulesLoaded();
                 log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules);
        
        From 85c4556e52d7137ae1fe1a3da04b26a311708836 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 10 Jan 2025 15:33:57 -0500
        Subject: [PATCH 078/122] Fixes checkstyle errors
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/resources/CreatedByTests.java    |  5 +++--
         .../resources/RecipientTypeRegistryTests.java | 10 +++++----
         .../security/resources/ShareWithTests.java    | 21 +++++++++++--------
         3 files changed, 21 insertions(+), 15 deletions(-)
        
        diff --git a/src/test/java/org/opensearch/security/resources/CreatedByTests.java b/src/test/java/org/opensearch/security/resources/CreatedByTests.java
        index 6b183ccbc7..346a949444 100644
        --- a/src/test/java/org/opensearch/security/resources/CreatedByTests.java
        +++ b/src/test/java/org/opensearch/security/resources/CreatedByTests.java
        @@ -19,17 +19,18 @@
         import org.opensearch.core.common.io.stream.StreamOutput;
         import org.opensearch.core.xcontent.XContentBuilder;
         import org.opensearch.core.xcontent.XContentParser;
        -import org.opensearch.test.OpenSearchTestCase;
        +import org.opensearch.security.test.SingleClusterTest;
         
         import static org.hamcrest.Matchers.equalTo;
         import static org.hamcrest.Matchers.greaterThan;
         import static org.hamcrest.Matchers.is;
         import static org.hamcrest.Matchers.notNullValue;
         import static org.hamcrest.Matchers.nullValue;
        +import static org.junit.Assert.assertThrows;
         import static org.mockito.Mockito.mock;
         import static org.mockito.Mockito.when;
         
        -public class CreatedByTests extends OpenSearchTestCase {
        +public class CreatedByTests extends SingleClusterTest {
         
             private static final String CREATOR_TYPE = "user";
         
        diff --git a/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java b/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java
        index 394bae608e..47151898d1 100644
        --- a/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java
        +++ b/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java
        @@ -10,12 +10,14 @@
         
         import org.hamcrest.MatcherAssert;
         
        -import org.opensearch.test.OpenSearchTestCase;
        +import org.opensearch.security.test.SingleClusterTest;
         
         import static org.hamcrest.Matchers.equalTo;
         import static org.hamcrest.Matchers.is;
        +import static org.hamcrest.Matchers.notNullValue;
        +import static org.junit.Assert.assertThrows;
         
        -public class RecipientTypeRegistryTests extends OpenSearchTestCase {
        +public class RecipientTypeRegistryTests extends SingleClusterTest {
         
             public void testFromValue() {
                 RecipientTypeRegistry.registerRecipientType("ble1", new RecipientType("ble1"));
        @@ -23,8 +25,8 @@ public void testFromValue() {
         
                 // Valid Value
                 RecipientType type = RecipientTypeRegistry.fromValue("ble1");
        -        assertNotNull(type);
        -        assertEquals("ble1", type.getType());
        +        MatcherAssert.assertThat(type, notNullValue());
        +        MatcherAssert.assertThat(type.getType(), is(equalTo("ble1")));
         
                 // Invalid Value
                 IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RecipientTypeRegistry.fromValue("bleble"));
        diff --git a/src/test/java/org/opensearch/security/resources/ShareWithTests.java b/src/test/java/org/opensearch/security/resources/ShareWithTests.java
        index 43b2b6f502..cec50a8198 100644
        --- a/src/test/java/org/opensearch/security/resources/ShareWithTests.java
        +++ b/src/test/java/org/opensearch/security/resources/ShareWithTests.java
        @@ -21,11 +21,12 @@
         import org.opensearch.common.xcontent.XContentType;
         import org.opensearch.common.xcontent.json.JsonXContent;
         import org.opensearch.core.common.io.stream.StreamOutput;
        +import org.opensearch.core.xcontent.NamedXContentRegistry;
         import org.opensearch.core.xcontent.ToXContent;
         import org.opensearch.core.xcontent.XContentBuilder;
         import org.opensearch.core.xcontent.XContentParser;
         import org.opensearch.security.spi.resources.ResourceAccessScope;
        -import org.opensearch.test.OpenSearchTestCase;
        +import org.opensearch.security.test.SingleClusterTest;
         
         import org.mockito.Mockito;
         
        @@ -33,6 +34,8 @@
         import static org.hamcrest.Matchers.empty;
         import static org.hamcrest.Matchers.equalTo;
         import static org.hamcrest.Matchers.is;
        +import static org.hamcrest.Matchers.notNullValue;
        +import static org.junit.Assert.assertThrows;
         import static org.mockito.Mockito.doThrow;
         import static org.mockito.Mockito.eq;
         import static org.mockito.Mockito.mock;
        @@ -40,7 +43,7 @@
         import static org.mockito.Mockito.verify;
         import static org.mockito.Mockito.when;
         
        -public class ShareWithTests extends OpenSearchTestCase {
        +public class ShareWithTests extends SingleClusterTest {
         
             @Before
             public void setupResourceRecipientTypes() {
        @@ -55,16 +58,16 @@ public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOExceptio
         
                 ShareWith shareWith = ShareWith.fromXContent(parser);
         
        -        assertNotNull(shareWith);
        +        MatcherAssert.assertThat(shareWith, notNullValue());
                 Set sharedWithScopes = shareWith.getSharedWithScopes();
        -        assertNotNull(sharedWithScopes);
        +        MatcherAssert.assertThat(sharedWithScopes, notNullValue());
                 MatcherAssert.assertThat(1, equalTo(sharedWithScopes.size()));
         
                 SharedWithScope scope = sharedWithScopes.iterator().next();
                 MatcherAssert.assertThat("read_only", equalTo(scope.getScope()));
         
                 SharedWithScope.ScopeRecipients scopeRecipients = scope.getSharedWithPerScope();
        -        assertNotNull(scopeRecipients);
        +        MatcherAssert.assertThat(scopeRecipients, notNullValue());
                 Map> recipients = scopeRecipients.getRecipients();
                 MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(1));
                 MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())), contains("user1"));
        @@ -77,11 +80,11 @@ public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOExceptio
         
             public void testFromXContentWithEmptyInput() throws IOException {
                 String emptyJson = "{}";
        -        XContentParser parser = XContentType.JSON.xContent().createParser(xContentRegistry(), null, emptyJson);
        +        XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, null, emptyJson);
         
                 ShareWith result = ShareWith.fromXContent(parser);
         
        -        assertNotNull(result);
        +        MatcherAssert.assertThat(result, notNullValue());
                 MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty()));
             }
         
        @@ -108,7 +111,7 @@ public void testFromXContentWithStartObject() throws IOException {
         
                 ShareWith shareWith = ShareWith.fromXContent(parser);
         
        -        assertNotNull(shareWith);
        +        MatcherAssert.assertThat(shareWith, notNullValue());
                 Set scopes = shareWith.getSharedWithScopes();
                 MatcherAssert.assertThat(scopes.size(), equalTo(2));
         
        @@ -152,7 +155,7 @@ public void testFromXContentWithUnexpectedEndOfInput() throws IOException {
         
                 ShareWith result = ShareWith.fromXContent(mockParser);
         
        -        assertNotNull(result);
        +        MatcherAssert.assertThat(result, notNullValue());
                 MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty()));
             }
         
        
        From 3e6531d89d5f4d34ab54820d472a1820697d46a4 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Mon, 13 Jan 2025 11:46:10 -0500
        Subject: [PATCH 079/122] Adds featureFlag for resource sharing
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/SearchOperationTest.java         |   2 +
         .../security/OpenSearchSecurityPlugin.java    |  60 ++++--
         .../configuration/DlsFlsValveImpl.java        |  10 +-
         .../SecurityFlsDlsIndexSearcherWrapper.java   |  20 +-
         .../resources/ResourceAccessHandler.java      |   7 +
         .../ResourceSharingIndexHandler.java          |   8 +-
         ...ourceSharingIndexManagementRepository.java |  25 ++-
         .../security/support/ConfigConstants.java     | 185 +++++++++---------
         .../security/SlowIntegrationTests.java        |   1 +
         9 files changed, 194 insertions(+), 124 deletions(-)
        
        diff --git a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java
        index cbb5ec11f0..adcd32f224 100644
        --- a/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java
        +++ b/src/integrationTest/java/org/opensearch/security/SearchOperationTest.java
        @@ -143,6 +143,7 @@
         import static org.opensearch.security.Song.TITLE_POISON;
         import static org.opensearch.security.Song.TITLE_SONG_1_PLUS_1;
         import static org.opensearch.security.auditlog.impl.AuditCategory.INDEX_EVENT;
        +import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED;
         import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
         import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS;
         import static org.opensearch.test.framework.audit.AuditMessagePredicate.auditPredicate;
        @@ -383,6 +384,7 @@ public class SearchOperationTest {
                     new AuditConfiguration(true).compliance(new AuditCompliance().enabled(true))
                         .filters(new AuditFilters().enabledRest(true).enabledTransport(true))
                 )
        +        .nodeSettings(Map.of(OPENSEARCH_RESOURCE_SHARING_ENABLED, false))
                 .build();
         
             @Rule
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index 68a1399cad..a65cc15f7d 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -696,14 +696,19 @@ public List getRestHandlers(
                         );
         
                         // Adds rest handlers for resource-access-control actions
        -                handlers.addAll(
        -                    List.of(
        -                        new RestShareResourceAction(),
        -                        new RestRevokeResourceAccessAction(),
        -                        new RestListAccessibleResourcesAction(),
        -                        new RestVerifyResourceAccessAction()
        -                    )
        -                );
        +                if (settings.getAsBoolean(
        +                    ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +                    ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +                )) {
        +                    handlers.addAll(
        +                        List.of(
        +                            new RestShareResourceAction(),
        +                            new RestRevokeResourceAccessAction(),
        +                            new RestListAccessibleResourcesAction(),
        +                            new RestVerifyResourceAccessAction()
        +                        )
        +                    );
        +                }
                         log.debug("Added {} rest handler(s)", handlers.size());
                     }
                 }
        @@ -733,14 +738,19 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre
                     actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class));
         
                     // Resource-access-control related actions
        -            actions.addAll(
        -                List.of(
        -                    new ActionHandler<>(ShareResourceAction.INSTANCE, TransportShareResourceAction.class),
        -                    new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, TransportRevokeResourceAccessAction.class),
        -                    new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, TransportListAccessibleResourcesAction.class),
        -                    new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, TransportVerifyResourceAccessAction.class)
        -                )
        -            );
        +            if (settings.getAsBoolean(
        +                ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +                ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +            )) {
        +                actions.addAll(
        +                    List.of(
        +                        new ActionHandler<>(ShareResourceAction.INSTANCE, TransportShareResourceAction.class),
        +                        new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, TransportRevokeResourceAccessAction.class),
        +                        new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, TransportListAccessibleResourcesAction.class),
        +                        new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, TransportVerifyResourceAccessAction.class)
        +                    )
        +                );
        +            }
                 }
                 return actions;
             }
        @@ -1271,8 +1281,12 @@ public Collection createComponents(
                 );
                 resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns);
                 resourceAccessHandler.initializeRecipientTypes();
        -
        -        rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler);
        +        // Resource Sharing index is enabled by default
        +        boolean isResourceSharingEnabled = settings.getAsBoolean(
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +        );
        +        rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler, isResourceSharingEnabled);
         
                 components.add(adminDns);
                 components.add(cr);
        @@ -2139,6 +2153,16 @@ public List> getSettings() {
         
                     // Privileges evaluation
                     settings.add(ActionPrivileges.PRECOMPUTED_PRIVILEGES_MAX_HEAP_SIZE);
        +
        +            // Resource Sharing
        +            settings.add(
        +                Setting.boolSetting(
        +                    ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +                    ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT,
        +                    Property.NodeScope,
        +                    Property.Filtered
        +                )
        +            );
                 }
         
                 return settings;
        diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        index c603f4c02f..22a05edcd0 100644
        --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        @@ -101,6 +101,7 @@ public class DlsFlsValveImpl implements DlsFlsRequestValve {
             private final FieldMasking.Config fieldMaskingConfig;
             private final Settings settings;
             private final ResourceAccessHandler resourceAccessHandler;
        +    private final boolean isResourceSharingEnabled;
         
             public DlsFlsValveImpl(
                 Settings settings,
        @@ -123,6 +124,10 @@ public DlsFlsValveImpl(
                 this.dlsFlsBaseContext = dlsFlsBaseContext;
                 this.settings = settings;
                 this.resourceAccessHandler = resourceAccessHandler;
        +        this.isResourceSharingEnabled = settings.getAsBoolean(
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +        );
         
                 clusterService.addListener(event -> {
                     DlsFlsProcessedConfig config = dlsFlsProcessedConfig.get();
        @@ -390,11 +395,8 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo
                     DlsRestriction dlsRestriction;
         
                     Set resourceIds;
        -            if (OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
        +            if (this.isResourceSharingEnabled && OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
                         resourceIds = this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(index);
        -                if (resourceIds.isEmpty()) {
        -                    return;
        -                }
                         // Create a DLS restriction to filter search results with accessible resources only
                         dlsRestriction = this.resourceAccessHandler.createResourceDLSRestriction(resourceIds, namedXContentRegistry);
         
        diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        index e76fba0b56..662476928d 100644
        --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        @@ -65,6 +65,7 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapp
             private final Supplier dlsFlsProcessedConfigSupplier;
             private final DlsFlsBaseContext dlsFlsBaseContext;
             private final ResourceAccessHandler resourceAccessHandler;
        +    private final boolean isResourceSharingEnabled;
         
             public SecurityFlsDlsIndexSearcherWrapper(
                 final IndexService indexService,
        @@ -109,6 +110,10 @@ public SecurityFlsDlsIndexSearcherWrapper(
                 this.dlsFlsProcessedConfigSupplier = dlsFlsProcessedConfigSupplier;
                 this.dlsFlsBaseContext = dlsFlsBaseContext;
                 this.resourceAccessHandler = resourceAccessHandler;
        +        this.isResourceSharingEnabled = settings.getAsBoolean(
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +        );
             }
         
             @SuppressWarnings("unchecked")
        @@ -123,9 +128,14 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm
                 }
         
                 String indexName = shardId != null ? shardId.getIndexName() : null;
        -        Set resourceIds = null;
        -        if (!Strings.isNullOrEmpty(indexName) && OpenSearchSecurityPlugin.getResourceIndices().contains(indexName)) {
        +        Set resourceIds;
        +        if (this.isResourceSharingEnabled
        +            && !Strings.isNullOrEmpty(indexName)
        +            && OpenSearchSecurityPlugin.getResourceIndices().contains(indexName)) {
                     resourceIds = this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(indexName);
        +            // resourceIds.isEmpty() indicates that the index is a resource index but the user does not have access to any resource under
        +            // the
        +            // index
                     if (resourceIds.isEmpty()) {
                         return new EmptyFilterLeafReader.EmptyDirectoryReader(reader);
                     }
        @@ -148,10 +158,7 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm
                     );
                 }
         
        -        // resourceIds == null indicates that the index is not a resource index
        -        // resourceIds.isEmpty() indicates that the index is a resource index but the user does not have access to any resource under the
        -        // index
        -        if (isAdmin || privilegesEvaluationContext == null || resourceIds == null) {
        +        if (isAdmin || privilegesEvaluationContext == null) {
                     return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(
                         reader,
                         FieldPrivileges.FlsRule.ALLOW_ALL,
        @@ -167,7 +174,6 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm
                 }
         
                 try {
        -
                     DlsFlsProcessedConfig config = this.dlsFlsProcessedConfigSupplier.get();
                     DlsRestriction dlsRestriction;
         
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        index 38b689bfad..47eaf65791 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        @@ -397,6 +397,13 @@ public Query createResourceDLSQuery(Set resourceIds, QueryShardContext q
              */
             public DlsRestriction createResourceDLSRestriction(Set resourceIds, NamedXContentRegistry xContentRegistry)
                 throws JsonProcessingException, PrivilegesConfigurationValidationException {
        +
        +        // resourceIds.isEmpty() is true when user doesn't have access to any resources
        +        if (resourceIds.isEmpty()) {
        +            LOGGER.debug("No resources found for user");
        +            return DlsRestriction.FULL;
        +        }
        +
                 String jsonQuery = String.format(
                     "{ \"bool\": { \"filter\": [ { \"terms\": { \"_id\": %s } } ] } }",
                     DefaultObjectMapper.writeValueAsString(resourceIds, true)
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        index 7d4a55b8ca..1847a6f3d1 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        @@ -123,12 +123,16 @@ public void createResourceSharingIndexIfAbsent(Callable callable) {
                     CreateIndexRequest cir = new CreateIndexRequest(resourceSharingIndex).settings(INDEX_SETTINGS).waitForActiveShards(1);
                     ActionListener cirListener = ActionListener.wrap(response -> {
                         LOGGER.info("Resource sharing index {} created.", resourceSharingIndex);
        -                callable.call();
        +                if (callable != null) {
        +                    callable.call();
        +                }
                     }, (failResponse) -> {
                         /* Index already exists, ignore and continue */
                         LOGGER.info("Index {} already exists.", resourceSharingIndex);
                         try {
        -                    callable.call();
        +                    if (callable != null) {
        +                        callable.call();
        +                    }
                         } catch (Exception e) {
                             throw new RuntimeException(e);
                         }
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java
        index 17f57269be..9ad7e18975 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexManagementRepository.java
        @@ -11,17 +11,29 @@
         
         package org.opensearch.security.resources;
         
        +import org.apache.logging.log4j.LogManager;
        +import org.apache.logging.log4j.Logger;
        +
         public class ResourceSharingIndexManagementRepository {
         
        +    private static final Logger log = LogManager.getLogger(ResourceSharingIndexManagementRepository.class);
        +
             private final ResourceSharingIndexHandler resourceSharingIndexHandler;
        +    private final boolean resourceSharingEnabled;
         
        -    protected ResourceSharingIndexManagementRepository(final ResourceSharingIndexHandler resourceSharingIndexHandler) {
        +    protected ResourceSharingIndexManagementRepository(
        +        final ResourceSharingIndexHandler resourceSharingIndexHandler,
        +        boolean isResourceSharingEnabled
        +    ) {
                 this.resourceSharingIndexHandler = resourceSharingIndexHandler;
        +        this.resourceSharingEnabled = isResourceSharingEnabled;
             }
         
        -    public static ResourceSharingIndexManagementRepository create(ResourceSharingIndexHandler resourceSharingIndexHandler) {
        -
        -        return new ResourceSharingIndexManagementRepository(resourceSharingIndexHandler);
        +    public static ResourceSharingIndexManagementRepository create(
        +        ResourceSharingIndexHandler resourceSharingIndexHandler,
        +        boolean isResourceSharingEnabled
        +    ) {
        +        return new ResourceSharingIndexManagementRepository(resourceSharingIndexHandler, isResourceSharingEnabled);
             }
         
             /**
        @@ -32,7 +44,10 @@ public static ResourceSharingIndexManagementRepository create(ResourceSharingInd
              */
             public void createResourceSharingIndexIfAbsent() {
                 // TODO check if this should be wrapped in an atomic completable future
        +        if (resourceSharingEnabled) {
        +            log.info("Attempting to create Resource Sharing index");
        +            this.resourceSharingIndexHandler.createResourceSharingIndexIfAbsent(() -> null);
        +        }
         
        -        this.resourceSharingIndexHandler.createResourceSharingIndexIfAbsent(() -> null);
             }
         }
        diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java
        index a510b79aed..dc0d68fea9 100644
        --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java
        +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java
        @@ -43,6 +43,7 @@
         public class ConfigConstants {
         
             public static final String OPENDISTRO_SECURITY_CONFIG_PREFIX = "_opendistro_security_";
        +    public static final String SECURITY_SETTINGS_PREFIX = "plugins.security.";
         
             public static final String OPENDISTRO_SECURITY_CHANNEL_TYPE = OPENDISTRO_SECURITY_CONFIG_PREFIX + "channel_type";
         
        @@ -126,11 +127,11 @@ public class ConfigConstants {
         
             public static final String OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX = ".opendistro_security";
         
        -    public static final String SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = "plugins.security.enable_snapshot_restore_privilege";
        +    public static final String SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = SECURITY_SETTINGS_PREFIX + "enable_snapshot_restore_privilege";
             public static final boolean SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = true;
         
        -    public static final String SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES =
        -        "plugins.security.check_snapshot_restore_write_privileges";
        +    public static final String SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES = SECURITY_SETTINGS_PREFIX
        +        + "check_snapshot_restore_write_privileges";
             public static final boolean SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES = true;
             public static final Set SECURITY_SNAPSHOT_RESTORE_NEEDED_WRITE_PRIVILEGES = Collections.unmodifiableSet(
                 new HashSet(Arrays.asList("indices:admin/create", "indices:data/write/index"
        @@ -138,37 +139,39 @@ public class ConfigConstants {
                 ))
             );
         
        -    public static final String SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = "plugins.security.cert.intercluster_request_evaluator_class";
        +    public static final String SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = SECURITY_SETTINGS_PREFIX
        +        + "cert.intercluster_request_evaluator_class";
             public static final String OPENDISTRO_SECURITY_ACTION_NAME = OPENDISTRO_SECURITY_CONFIG_PREFIX + "action_name";
         
        -    public static final String SECURITY_AUTHCZ_ADMIN_DN = "plugins.security.authcz.admin_dn";
        -    public static final String SECURITY_CONFIG_INDEX_NAME = "plugins.security.config_index_name";
        -    public static final String SECURITY_AUTHCZ_IMPERSONATION_DN = "plugins.security.authcz.impersonation_dn";
        -    public static final String SECURITY_AUTHCZ_REST_IMPERSONATION_USERS = "plugins.security.authcz.rest_impersonation_user";
        +    public static final String SECURITY_AUTHCZ_ADMIN_DN = SECURITY_SETTINGS_PREFIX + "authcz.admin_dn";
        +    public static final String SECURITY_CONFIG_INDEX_NAME = SECURITY_SETTINGS_PREFIX + "config_index_name";
        +    public static final String SECURITY_AUTHCZ_IMPERSONATION_DN = SECURITY_SETTINGS_PREFIX + "authcz.impersonation_dn";
        +    public static final String SECURITY_AUTHCZ_REST_IMPERSONATION_USERS = SECURITY_SETTINGS_PREFIX + "authcz.rest_impersonation_user";
         
             public static final String BCRYPT = "bcrypt";
             public static final String PBKDF2 = "pbkdf2";
         
        -    public static final String SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS = "plugins.security.password.hashing.bcrypt.rounds";
        +    public static final String SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS = SECURITY_SETTINGS_PREFIX + "password.hashing.bcrypt.rounds";
             public static final int SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS_DEFAULT = 12;
        -    public static final String SECURITY_PASSWORD_HASHING_BCRYPT_MINOR = "plugins.security.password.hashing.bcrypt.minor";
        +    public static final String SECURITY_PASSWORD_HASHING_BCRYPT_MINOR = SECURITY_SETTINGS_PREFIX + "password.hashing.bcrypt.minor";
             public static final String SECURITY_PASSWORD_HASHING_BCRYPT_MINOR_DEFAULT = "Y";
         
        -    public static final String SECURITY_PASSWORD_HASHING_ALGORITHM = "plugins.security.password.hashing.algorithm";
        +    public static final String SECURITY_PASSWORD_HASHING_ALGORITHM = SECURITY_SETTINGS_PREFIX + "password.hashing.algorithm";
             public static final String SECURITY_PASSWORD_HASHING_ALGORITHM_DEFAULT = BCRYPT;
        -    public static final String SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS = "plugins.security.password.hashing.pbkdf2.iterations";
        +    public static final String SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS = SECURITY_SETTINGS_PREFIX
        +        + "password.hashing.pbkdf2.iterations";
             public static final int SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT = 600_000;
        -    public static final String SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH = "plugins.security.password.hashing.pbkdf2.length";
        +    public static final String SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH = SECURITY_SETTINGS_PREFIX + "password.hashing.pbkdf2.length";
             public static final int SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT = 256;
        -    public static final String SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION = "plugins.security.password.hashing.pbkdf2.function";
        +    public static final String SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION = SECURITY_SETTINGS_PREFIX + "password.hashing.pbkdf2.function";
             public static final String SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT = Hmac.SHA256.name();
         
        -    public static final String SECURITY_AUDIT_TYPE_DEFAULT = "plugins.security.audit.type";
        -    public static final String SECURITY_AUDIT_CONFIG_DEFAULT = "plugins.security.audit.config";
        -    public static final String SECURITY_AUDIT_CONFIG_ROUTES = "plugins.security.audit.routes";
        -    public static final String SECURITY_AUDIT_CONFIG_ENDPOINTS = "plugins.security.audit.endpoints";
        -    public static final String SECURITY_AUDIT_THREADPOOL_SIZE = "plugins.security.audit.threadpool.size";
        -    public static final String SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN = "plugins.security.audit.threadpool.max_queue_len";
        +    public static final String SECURITY_AUDIT_TYPE_DEFAULT = SECURITY_SETTINGS_PREFIX + "audit.type";
        +    public static final String SECURITY_AUDIT_CONFIG_DEFAULT = SECURITY_SETTINGS_PREFIX + "audit.config";
        +    public static final String SECURITY_AUDIT_CONFIG_ROUTES = SECURITY_SETTINGS_PREFIX + "audit.routes";
        +    public static final String SECURITY_AUDIT_CONFIG_ENDPOINTS = SECURITY_SETTINGS_PREFIX + "audit.endpoints";
        +    public static final String SECURITY_AUDIT_THREADPOOL_SIZE = SECURITY_SETTINGS_PREFIX + "audit.threadpool.size";
        +    public static final String SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN = SECURITY_SETTINGS_PREFIX + "audit.threadpool.max_queue_len";
             public static final String OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY = "opendistro_security.audit.log_request_body";
             public static final String OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES = "opendistro_security.audit.resolve_indices";
             public static final String OPENDISTRO_SECURITY_AUDIT_ENABLE_REST = "opendistro_security.audit.enable_rest";
        @@ -183,13 +186,13 @@ public class ConfigConstants {
             );
             public static final String OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS = "opendistro_security.audit.ignore_users";
             public static final String OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS = "opendistro_security.audit.ignore_requests";
        -    public static final String SECURITY_AUDIT_IGNORE_HEADERS = "plugins.security.audit.ignore_headers";
        +    public static final String SECURITY_AUDIT_IGNORE_HEADERS = SECURITY_SETTINGS_PREFIX + "audit.ignore_headers";
             public static final String OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS = "opendistro_security.audit.resolve_bulk_requests";
             public static final boolean OPENDISTRO_SECURITY_AUDIT_SSL_VERIFY_HOSTNAMES_DEFAULT = true;
             public static final boolean OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT = false;
             public static final String OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS = "opendistro_security.audit.exclude_sensitive_headers";
         
        -    public static final String SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX = "plugins.security.audit.config.";
        +    public static final String SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX = SECURITY_SETTINGS_PREFIX + "audit.config.";
         
             // Internal Opensearch data_stream
             public static final String SECURITY_AUDIT_OPENSEARCH_DATASTREAM_NAME = "data_stream.name";
        @@ -232,31 +235,31 @@ public class ConfigConstants {
             public static final String SECURITY_AUDIT_LOG4J_LEVEL = "log4j.level";
         
             // retry
        -    public static final String SECURITY_AUDIT_RETRY_COUNT = "plugins.security.audit.config.retry_count";
        -    public static final String SECURITY_AUDIT_RETRY_DELAY_MS = "plugins.security.audit.config.retry_delay_ms";
        +    public static final String SECURITY_AUDIT_RETRY_COUNT = SECURITY_SETTINGS_PREFIX + "audit.config.retry_count";
        +    public static final String SECURITY_AUDIT_RETRY_DELAY_MS = SECURITY_SETTINGS_PREFIX + "audit.config.retry_delay_ms";
         
        -    public static final String SECURITY_KERBEROS_KRB5_FILEPATH = "plugins.security.kerberos.krb5_filepath";
        -    public static final String SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH = "plugins.security.kerberos.acceptor_keytab_filepath";
        -    public static final String SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL = "plugins.security.kerberos.acceptor_principal";
        -    public static final String SECURITY_CERT_OID = "plugins.security.cert.oid";
        -    public static final String SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS =
        -        "plugins.security.cert.intercluster_request_evaluator_class";
        -    public static final String SECURITY_ADVANCED_MODULES_ENABLED = "plugins.security.advanced_modules_enabled";
        -    public static final String SECURITY_NODES_DN = "plugins.security.nodes_dn";
        -    public static final String SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED = "plugins.security.nodes_dn_dynamic_config_enabled";
        -    public static final String SECURITY_DISABLED = "plugins.security.disabled";
        +    public static final String SECURITY_KERBEROS_KRB5_FILEPATH = SECURITY_SETTINGS_PREFIX + "kerberos.krb5_filepath";
        +    public static final String SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH = SECURITY_SETTINGS_PREFIX + "kerberos.acceptor_keytab_filepath";
        +    public static final String SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL = SECURITY_SETTINGS_PREFIX + "kerberos.acceptor_principal";
        +    public static final String SECURITY_CERT_OID = SECURITY_SETTINGS_PREFIX + "cert.oid";
        +    public static final String SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = SECURITY_SETTINGS_PREFIX
        +        + "cert.intercluster_request_evaluator_class";
        +    public static final String SECURITY_ADVANCED_MODULES_ENABLED = SECURITY_SETTINGS_PREFIX + "advanced_modules_enabled";
        +    public static final String SECURITY_NODES_DN = SECURITY_SETTINGS_PREFIX + "nodes_dn";
        +    public static final String SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED = SECURITY_SETTINGS_PREFIX + "nodes_dn_dynamic_config_enabled";
        +    public static final String SECURITY_DISABLED = SECURITY_SETTINGS_PREFIX + "disabled";
         
        -    public static final String SECURITY_CACHE_TTL_MINUTES = "plugins.security.cache.ttl_minutes";
        -    public static final String SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES = "plugins.security.allow_unsafe_democertificates";
        -    public static final String SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX = "plugins.security.allow_default_init_securityindex";
        +    public static final String SECURITY_CACHE_TTL_MINUTES = SECURITY_SETTINGS_PREFIX + "cache.ttl_minutes";
        +    public static final String SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES = SECURITY_SETTINGS_PREFIX + "allow_unsafe_democertificates";
        +    public static final String SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX = SECURITY_SETTINGS_PREFIX + "allow_default_init_securityindex";
         
        -    public static final String SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE =
        -        "plugins.security.allow_default_init_securityindex.use_cluster_state";
        +    public static final String SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE = SECURITY_SETTINGS_PREFIX
        +        + "allow_default_init_securityindex.use_cluster_state";
         
        -    public static final String SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST =
        -        "plugins.security.background_init_if_securityindex_not_exist";
        +    public static final String SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST = SECURITY_SETTINGS_PREFIX
        +        + "background_init_if_securityindex_not_exist";
         
        -    public static final String SECURITY_ROLES_MAPPING_RESOLUTION = "plugins.security.roles_mapping_resolution";
        +    public static final String SECURITY_ROLES_MAPPING_RESOLUTION = SECURITY_SETTINGS_PREFIX + "roles_mapping_resolution";
         
             public static final String OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY =
                 "opendistro_security.compliance.history.write.metadata_only";
        @@ -275,21 +278,22 @@ public class ConfigConstants {
             public static final String OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED =
                 "opendistro_security.compliance.history.external_config_enabled";
             public static final String OPENDISTRO_SECURITY_SOURCE_FIELD_CONTEXT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "source_field_context";
        -    public static final String SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION =
        -        "plugins.security.compliance.disable_anonymous_authentication";
        -    public static final String SECURITY_COMPLIANCE_IMMUTABLE_INDICES = "plugins.security.compliance.immutable_indices";
        -    public static final String SECURITY_COMPLIANCE_SALT = "plugins.security.compliance.salt";
        +    public static final String SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION = SECURITY_SETTINGS_PREFIX
        +        + "compliance.disable_anonymous_authentication";
        +    public static final String SECURITY_COMPLIANCE_IMMUTABLE_INDICES = SECURITY_SETTINGS_PREFIX + "compliance.immutable_indices";
        +    public static final String SECURITY_COMPLIANCE_SALT = SECURITY_SETTINGS_PREFIX + "compliance.salt";
             public static final String SECURITY_COMPLIANCE_SALT_DEFAULT = "e1ukloTsQlOgPquJ";// 16 chars
             public static final String SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED =
                 "opendistro_security.compliance.history.internal_config_enabled";
        -    public static final String SECURITY_SSL_ONLY = "plugins.security.ssl_only";
        +    public static final String SECURITY_SSL_ONLY = SECURITY_SETTINGS_PREFIX + "ssl_only";
             public static final String SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED = "plugins.security_config.ssl_dual_mode_enabled";
             public static final String SECURITY_SSL_DUAL_MODE_SKIP_SECURITY = OPENDISTRO_SECURITY_CONFIG_PREFIX + "passive_security";
             public static final String LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED = "opendistro_security_config.ssl_dual_mode_enabled";
        -    public static final String SECURITY_SSL_CERT_RELOAD_ENABLED = "plugins.security.ssl_cert_reload_enabled";
        -    public static final String SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED = "plugins.security.ssl.certificates_hot_reload.enabled";
        -    public static final String SECURITY_DISABLE_ENVVAR_REPLACEMENT = "plugins.security.disable_envvar_replacement";
        -    public static final String SECURITY_DFM_EMPTY_OVERRIDES_ALL = "plugins.security.dfm_empty_overrides_all";
        +    public static final String SECURITY_SSL_CERT_RELOAD_ENABLED = SECURITY_SETTINGS_PREFIX + "ssl_cert_reload_enabled";
        +    public static final String SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED = SECURITY_SETTINGS_PREFIX
        +        + "ssl.certificates_hot_reload.enabled";
        +    public static final String SECURITY_DISABLE_ENVVAR_REPLACEMENT = SECURITY_SETTINGS_PREFIX + "disable_envvar_replacement";
        +    public static final String SECURITY_DFM_EMPTY_OVERRIDES_ALL = SECURITY_SETTINGS_PREFIX + "dfm_empty_overrides_all";
         
             public enum RolesMappingResolution {
                 MAPPING_ONLY,
        @@ -297,44 +301,46 @@ public enum RolesMappingResolution {
                 BOTH
             }
         
        -    public static final String SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS = "plugins.security.filter_securityindex_from_all_requests";
        -    public static final String SECURITY_DLS_MODE = "plugins.security.dls.mode";
        +    public static final String SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS = SECURITY_SETTINGS_PREFIX
        +        + "filter_securityindex_from_all_requests";
        +    public static final String SECURITY_DLS_MODE = SECURITY_SETTINGS_PREFIX + "dls.mode";
             // REST API
        -    public static final String SECURITY_RESTAPI_ROLES_ENABLED = "plugins.security.restapi.roles_enabled";
        -    public static final String SECURITY_RESTAPI_ADMIN_ENABLED = "plugins.security.restapi.admin.enabled";
        -    public static final String SECURITY_RESTAPI_ENDPOINTS_DISABLED = "plugins.security.restapi.endpoints_disabled";
        -    public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX = "plugins.security.restapi.password_validation_regex";
        -    public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE =
        -        "plugins.security.restapi.password_validation_error_message";
        -    public static final String SECURITY_RESTAPI_PASSWORD_MIN_LENGTH = "plugins.security.restapi.password_min_length";
        -    public static final String SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH =
        -        "plugins.security.restapi.password_score_based_validation_strength";
        +    public static final String SECURITY_RESTAPI_ROLES_ENABLED = SECURITY_SETTINGS_PREFIX + "restapi.roles_enabled";
        +    public static final String SECURITY_RESTAPI_ADMIN_ENABLED = SECURITY_SETTINGS_PREFIX + "restapi.admin.enabled";
        +    public static final String SECURITY_RESTAPI_ENDPOINTS_DISABLED = SECURITY_SETTINGS_PREFIX + "restapi.endpoints_disabled";
        +    public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX = SECURITY_SETTINGS_PREFIX + "restapi.password_validation_regex";
        +    public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE = SECURITY_SETTINGS_PREFIX
        +        + "restapi.password_validation_error_message";
        +    public static final String SECURITY_RESTAPI_PASSWORD_MIN_LENGTH = SECURITY_SETTINGS_PREFIX + "restapi.password_min_length";
        +    public static final String SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH = SECURITY_SETTINGS_PREFIX
        +        + "restapi.password_score_based_validation_strength";
             // Illegal Opcodes from here on
        -    public static final String SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY =
        -        "plugins.security.unsupported.disable_rest_auth_initially";
        -    public static final String SECURITY_UNSUPPORTED_DELAY_INITIALIZATION_SECONDS =
        -        "plugins.security.unsupported.delay_initialization_seconds";
        -    public static final String SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY =
        -        "plugins.security.unsupported.disable_intertransport_auth_initially";
        -    public static final String SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY =
        -        "plugins.security.unsupported.passive_intertransport_auth_initially";
        -    public static final String SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED =
        -        "plugins.security.unsupported.restore.securityindex.enabled";
        -    public static final String SECURITY_UNSUPPORTED_INJECT_USER_ENABLED = "plugins.security.unsupported.inject_user.enabled";
        -    public static final String SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED = "plugins.security.unsupported.inject_user.admin.enabled";
        -    public static final String SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS = "plugins.security.unsupported.allow_now_in_dls";
        -
        -    public static final String SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION =
        -        "plugins.security.unsupported.restapi.allow_securityconfig_modification";
        -    public static final String SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES = "plugins.security.unsupported.load_static_resources";
        -    public static final String SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG = "plugins.security.unsupported.accept_invalid_config";
        +    public static final String SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.disable_rest_auth_initially";
        +    public static final String SECURITY_UNSUPPORTED_DELAY_INITIALIZATION_SECONDS = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.delay_initialization_seconds";
        +    public static final String SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.disable_intertransport_auth_initially";
        +    public static final String SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.passive_intertransport_auth_initially";
        +    public static final String SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.restore.securityindex.enabled";
        +    public static final String SECURITY_UNSUPPORTED_INJECT_USER_ENABLED = SECURITY_SETTINGS_PREFIX + "unsupported.inject_user.enabled";
        +    public static final String SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.inject_user.admin.enabled";
        +    public static final String SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS = SECURITY_SETTINGS_PREFIX + "unsupported.allow_now_in_dls";
        +
        +    public static final String SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION = SECURITY_SETTINGS_PREFIX
        +        + "unsupported.restapi.allow_securityconfig_modification";
        +    public static final String SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES = SECURITY_SETTINGS_PREFIX + "unsupported.load_static_resources";
        +    public static final String SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG = SECURITY_SETTINGS_PREFIX + "unsupported.accept_invalid_config";
         
             // Protected indices settings. Marked for deprecation, after all config indices move to System indices.
        -    public static final String SECURITY_PROTECTED_INDICES_ENABLED_KEY = "plugins.security.protected_indices.enabled";
        +    public static final String SECURITY_PROTECTED_INDICES_ENABLED_KEY = SECURITY_SETTINGS_PREFIX + "protected_indices.enabled";
             public static final Boolean SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT = false;
        -    public static final String SECURITY_PROTECTED_INDICES_KEY = "plugins.security.protected_indices.indices";
        +    public static final String SECURITY_PROTECTED_INDICES_KEY = SECURITY_SETTINGS_PREFIX + "protected_indices.indices";
             public static final List SECURITY_PROTECTED_INDICES_DEFAULT = Collections.emptyList();
        -    public static final String SECURITY_PROTECTED_INDICES_ROLES_KEY = "plugins.security.protected_indices.roles";
        +    public static final String SECURITY_PROTECTED_INDICES_ROLES_KEY = SECURITY_SETTINGS_PREFIX + "protected_indices.roles";
             public static final List SECURITY_PROTECTED_INDICES_ROLES_DEFAULT = Collections.emptyList();
         
             // Roles injection for plugins
        @@ -348,19 +354,20 @@ public enum RolesMappingResolution {
         
             // System indices settings
             public static final String SYSTEM_INDEX_PERMISSION = "system:admin/system_index";
        -    public static final String SECURITY_SYSTEM_INDICES_ENABLED_KEY = "plugins.security.system_indices.enabled";
        +    public static final String SECURITY_SYSTEM_INDICES_ENABLED_KEY = SECURITY_SETTINGS_PREFIX + "system_indices.enabled";
             public static final Boolean SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT = false;
        -    public static final String SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY = "plugins.security.system_indices.permission.enabled";
        +    public static final String SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY = SECURITY_SETTINGS_PREFIX
        +        + "system_indices.permission.enabled";
             public static final Boolean SECURITY_SYSTEM_INDICES_PERMISSIONS_DEFAULT = false;
        -    public static final String SECURITY_SYSTEM_INDICES_KEY = "plugins.security.system_indices.indices";
        +    public static final String SECURITY_SYSTEM_INDICES_KEY = SECURITY_SETTINGS_PREFIX + "system_indices.indices";
             public static final List SECURITY_SYSTEM_INDICES_DEFAULT = Collections.emptyList();
        -    public static final String SECURITY_MASKED_FIELDS_ALGORITHM_DEFAULT = "plugins.security.masked_fields.algorithm.default";
        +    public static final String SECURITY_MASKED_FIELDS_ALGORITHM_DEFAULT = SECURITY_SETTINGS_PREFIX + "masked_fields.algorithm.default";
         
             public static final String TENANCY_PRIVATE_TENANT_NAME = "private";
             public static final String TENANCY_GLOBAL_TENANT_NAME = "global";
             public static final String TENANCY_GLOBAL_TENANT_DEFAULT_NAME = "";
         
        -    public static final String USE_JDK_SERIALIZATION = "plugins.security.use_jdk_serialization";
        +    public static final String USE_JDK_SERIALIZATION = SECURITY_SETTINGS_PREFIX + "use_jdk_serialization";
         
             // On-behalf-of endpoints settings
             // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
        @@ -373,6 +380,8 @@ public enum RolesMappingResolution {
         
             // Resource sharing index
             public static final String OPENSEARCH_RESOURCE_SHARING_INDEX = ".opensearch_resource_sharing";
        +    public static final String OPENSEARCH_RESOURCE_SHARING_ENABLED = SECURITY_SETTINGS_PREFIX + "resource_sharing.enabled";
        +    public static final boolean OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT = false;
         
             public static Set getSettingAsSet(
                 final Settings settings,
        diff --git a/src/test/java/org/opensearch/security/SlowIntegrationTests.java b/src/test/java/org/opensearch/security/SlowIntegrationTests.java
        index 74e3bfa9e4..99ac9fb1b9 100644
        --- a/src/test/java/org/opensearch/security/SlowIntegrationTests.java
        +++ b/src/test/java/org/opensearch/security/SlowIntegrationTests.java
        @@ -66,6 +66,7 @@ public void testCustomInterclusterRequestEvaluator() throws Exception {
                         ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS,
                         "org.opensearch.security.AlwaysFalseInterClusterRequestEvaluator"
                     )
        +            .put(ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, false)
                     .put("discovery.initial_state_timeout", "8s")
                     .build();
                 setup(Settings.EMPTY, null, settings, false, ClusterConfiguration.DEFAULT, 5, 1);
        
        From 5cab8af6ea51234c51784ffd8282900634de296d Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Mon, 13 Jan 2025 15:04:07 -0500
        Subject: [PATCH 080/122] Fixes spotbugsintegtest error
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/DoNotFailOnForbiddenTests.java      | 16 ++++++++++------
         1 file changed, 10 insertions(+), 6 deletions(-)
        
        diff --git a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java
        index 456d1ebada..4aa6005beb 100644
        --- a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java
        +++ b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java
        @@ -12,6 +12,7 @@
         import java.io.BufferedReader;
         import java.io.IOException;
         import java.io.InputStreamReader;
        +import java.nio.charset.StandardCharsets;
         import java.util.List;
         import java.util.stream.Collectors;
         
        @@ -462,8 +463,9 @@ public void shouldPerformCatIndices_positive() throws IOException {
                     Request getIndicesRequest = new Request("GET", "/_cat/indices");
                     // High level client doesn't support _cat/_indices API
                     Response getIndicesResponse = restHighLevelClient.getLowLevelClient().performRequest(getIndicesRequest);
        -            List indexes = new BufferedReader(new InputStreamReader(getIndicesResponse.getEntity().getContent())).lines()
        -                .collect(Collectors.toList());
        +            List indexes = new BufferedReader(
        +                new InputStreamReader(getIndicesResponse.getEntity().getContent(), StandardCharsets.UTF_8)
        +            ).lines().collect(Collectors.toList());
         
                     assertThat(indexes.size(), equalTo(1));
                     assertThat(indexes.get(0), containsString("marvelous_songs"));
        @@ -476,8 +478,9 @@ public void shouldPerformCatAliases_positive() throws IOException {
                 try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) {
                     Request getAliasesRequest = new Request("GET", "/_cat/aliases");
                     Response getAliasesResponse = restHighLevelClient.getLowLevelClient().performRequest(getAliasesRequest);
        -            List aliases = new BufferedReader(new InputStreamReader(getAliasesResponse.getEntity().getContent())).lines()
        -                .collect(Collectors.toList());
        +            List aliases = new BufferedReader(
        +                new InputStreamReader(getAliasesResponse.getEntity().getContent(), StandardCharsets.UTF_8)
        +            ).lines().collect(Collectors.toList());
         
                     // Does not fail on forbidden, but alias response only contains index which user has access to
                     assertThat(getAliasesResponse.getStatusLine().getStatusCode(), equalTo(200));
        @@ -490,8 +493,9 @@ public void shouldPerformCatAliases_positive() throws IOException {
                 try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) {
                     Request getAliasesRequest = new Request("GET", "/_cat/aliases");
                     Response getAliasesResponse = restHighLevelClient.getLowLevelClient().performRequest(getAliasesRequest);
        -            List aliases = new BufferedReader(new InputStreamReader(getAliasesResponse.getEntity().getContent())).lines()
        -                .collect(Collectors.toList());
        +            List aliases = new BufferedReader(
        +                new InputStreamReader(getAliasesResponse.getEntity().getContent(), StandardCharsets.UTF_8)
        +            ).lines().collect(Collectors.toList());
         
                     // Admin has access to all
                     assertThat(getAliasesResponse.getStatusLine().getStatusCode(), equalTo(200));
        
        From 8366a059cdf181cc8b59ebdce50d37589c2a5199 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Mon, 13 Jan 2025 15:47:40 -0500
        Subject: [PATCH 081/122] Fixes SafeSerializationUtils test
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/support/SafeSerializationUtilsTest.java        | 6 ++++++
         1 file changed, 6 insertions(+)
        
        diff --git a/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java b/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java
        index f69d4e0291..187fd8b372 100644
        --- a/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java
        +++ b/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java
        @@ -17,6 +17,7 @@
         import java.util.HashMap;
         import java.util.regex.Pattern;
         
        +import org.junit.After;
         import org.junit.Test;
         
         import org.opensearch.security.auth.UserInjector;
        @@ -35,6 +36,11 @@
         
         public class SafeSerializationUtilsTest {
         
        +    @After
        +    public void clearCache() {
        +        SafeSerializationUtils.safeClassCache.clear();
        +    }
        +
             @Test
             public void testSafeClasses() {
                 assertTrue(SafeSerializationUtils.isSafeClass(String.class));
        
        From 534838f235fe9456410f1800aa85bb6009cdfb12 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Mon, 13 Jan 2025 16:36:10 -0500
        Subject: [PATCH 082/122] Fix CI workflow that called assemble instead of
         :assemble and adds SPI to maven publish task and updates SPI readme
        
        Signed-off-by: Darshit Chanpura 
        ---
         .github/actions/create-bwc-build/action.yaml |   2 +-
         .github/workflows/ci.yml                     |  10 +-
         .github/workflows/maven-publish.yml          |   3 +-
         .github/workflows/plugin_install.yml         |   2 +-
         spi/README.md                                | 145 +------------------
         spi/build.gradle                             |   9 +-
         6 files changed, 19 insertions(+), 152 deletions(-)
        
        diff --git a/.github/actions/create-bwc-build/action.yaml b/.github/actions/create-bwc-build/action.yaml
        index 8960849333..0f9e373b16 100644
        --- a/.github/actions/create-bwc-build/action.yaml
        +++ b/.github/actions/create-bwc-build/action.yaml
        @@ -42,7 +42,7 @@ runs:
               uses: gradle/gradle-build-action@v2
               with:
                 cache-disabled: true
        -        arguments: assemble
        +        arguments: :assemble
                 build-root-directory: ${{ inputs.plugin-branch }}
         
             - id: get-opensearch-version
        diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
        index 5a41062883..9919075cc6 100644
        --- a/.github/workflows/ci.yml
        +++ b/.github/workflows/ci.yml
        @@ -208,7 +208,7 @@ jobs:
             - uses: github/codeql-action/init@v3
               with:
                 languages: java
        -    - run: ./gradlew clean assemble
        +    - run: ./gradlew clean :assemble
             - uses: github/codeql-action/analyze@v3
         
           build-artifact-names:
        @@ -238,13 +238,13 @@ jobs:
                 echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}
                 echo ${{ env.TEST_QUALIFIER }}
         
        -    - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip
        +    - run: ./gradlew clean :assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip
         
        -    - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip
        +    - run: ./gradlew clean :assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip
         
        -    - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip
        +    - run: ./gradlew clean :assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip
         
        -    - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip
        +    - run: ./gradlew clean :assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip
         
             - run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom
         
        diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml
        index d10fd67beb..2d4e7e1df0 100644
        --- a/.github/workflows/maven-publish.yml
        +++ b/.github/workflows/maven-publish.yml
        @@ -32,4 +32,5 @@ jobs:
                   export SONATYPE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-password --query SecretString --output text)
                   echo "::add-mask::$SONATYPE_USERNAME"
                   echo "::add-mask::$SONATYPE_PASSWORD"
        -          ./gradlew publishPluginZipPublicationToSnapshotsRepository
        +          ./gradlew --no-daemon publishPluginZipPublicationToSnapshotsRepository
        +          ./gradlew --no-daemon :opensearch-resource-sharing-spi:publishMavenJavaPublicationToSnapshotsRepository
        diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml
        index 3f8d61795c..c427b160c4 100644
        --- a/.github/workflows/plugin_install.yml
        +++ b/.github/workflows/plugin_install.yml
        @@ -32,7 +32,7 @@ jobs:
                 uses: gradle/gradle-build-action@v3
                 with:
                   cache-disabled: true
        -          arguments: assemble
        +          arguments: :assemble
         
               # Move and rename the plugin for installation
               - name: Move and rename the plugin for installation
        diff --git a/spi/README.md b/spi/README.md
        index ccd73db983..38efb1cf85 100644
        --- a/spi/README.md
        +++ b/spi/README.md
        @@ -1,147 +1,6 @@
        -# Resource Sharing and Access Control Plugin
        +# Resource Sharing and Access Control SPI
         
        -This plugin demonstrates resource sharing and access control functionality, providing APIs to create, manage, and verify access to resources. The plugin enables fine-grained permissions for sharing and accessing resources, making it suitable for systems requiring robust security and collaboration.
        -
        -## Features
        -
        -- Create and delete resources.
        -- Share resources with specific users, roles and/or backend_roles with specific scope(s).
        -- Revoke access to shared resources for a list of or all scopes.
        -- Verify access permissions for a given user within a given scope.
        -- List all resources accessible to current user.
        -
        -## API Endpoints
        -
        -The plugin exposes the following six API endpoints:
        -
        -### 1. Create Resource
        -- **Endpoint:** `POST /_plugins/sample_resource_sharing/create`
        -- **Description:** Creates a new resource. Also creates a resource sharing entry if security plugin is enabled.
        -- **Request Body:**
        -  ```json
        -  {
        -    "name": ""
        -  }
        -  ```
        -- **Response:**
        -  ```json
        -  {
        -    "message": "Resource  created successfully."
        -  }
        -  ```
        -
        -### 2. Delete Resource
        -- **Endpoint:** `DELETE /_plugins/sample_resource_sharing/{resource_id}`
        -- **Description:** Deletes a specified resource owned by the requesting user.
        -- **Response:**
        -  ```json
        -  {
        -    "message": "Resource  deleted successfully."
        -  }
        -  ```
        -
        -### 3. Share Resource
        -- **Endpoint:** `POST /_plugins/sample_resource_sharing/share`
        -- **Description:** Shares a resource with specified users or roles with defined scope.
        -- **Request Body:**
        -  ```json
        -    {
        -      "resource_id" :  "{{ADMIN_RESOURCE_ID}}",
        -      "share_with" : {
        -        "SAMPLE_FULL_ACCESS": {
        -            "users": ["test"],
        -            "roles": ["test_role"],
        -            "backend_roles": ["test_backend_role"]
        -        },
        -        "READ_ONLY": {
        -            "users": ["test"],
        -            "roles": ["test_role"],
        -            "backend_roles": ["test_backend_role"]
        -        },
        -        "READ_WRITE": {
        -            "users": ["test"],
        -            "roles": ["test_role"],
        -            "backend_roles": ["test_backend_role"]
        -        }
        -      }
        -    }
        -  ```
        -- **Response:**
        -  ```json
        -    {
        -    "message": "Resource  shared successfully."
        -    }
        -  ```
        -
        -### 4. Revoke Access
        -- **Endpoint:** `POST /_plugins/sample_resource_sharing/revoke`
        -- **Description:** Revokes access to a resource for specified users or roles.
        -- **Request Body:**
        -  ```json
        -    {
        -      "resource_id" :  "",
        -      "entities" : {
        -            "users": ["test", "admin"],
        -            "roles": ["test_role", "all_access"],
        -            "backend_roles": ["test_backend_role", "admin"]
        -      },
        -      "scopes": ["SAMPLE_FULL_ACCESS", "READ_ONLY", "READ_WRITE"]
        -    }
        -  ```
        -- **Response:**
        -  ```json
        -    {
        -      "message": "Resource  access revoked successfully."
        -    }
        -  ```
        -
        -### 5. Verify Access
        -- **Endpoint:** `GET /_plugins/sample_resource_sharing/verify_resource_access`
        -- **Description:** Verifies if a user or role has access to a specific resource with a specific scope.
        -- **Request Body:**
        -    ```json
        -    {
        -      "resource_id": "",
        -      "scope": "SAMPLE_FULL_ACCESS"
        -    }
        -    ```
        -- **Response:**
        -  ```json
        -  {
        -    "message": "User has requested scope SAMPLE_FULL_ACCESS access to "
        -  }
        -  ```
        -
        -### 6. List Accessible Resources
        -- **Endpoint:** `GET /_plugins/sample_resource_sharing/list`
        -- **Description:** Lists all resources accessible to the requesting user or role.
        -- **Response:**
        -  ```json
        -  {
        -    "resource-ids": [
        -        "",
        -        ""
        -    ]
        -  }
        -  ```
        -
        -## Installation
        -
        -1. Clone the repository:
        -   ```bash
        -   git clone git@github.com:opensearch-project/security.git
        -   ```
        -
        -2. Navigate to the project directory:
        -   ```bash
        -   cd sample-resource-plugin
        -   ```
        -
        -3. Build and deploy the plugin:
        -   ```bash
        -   $ ./gradlew clean build -x test -x integrationTest -x spotbugsIntegrationTest
        -   $ ./bin/opensearch-plugin install file: /sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-3.0.0.0-SNAPSHOT.zip
        -   ```
        +This SPI provides interfaces to implement Resource Sharing and Access Control.
         
         ## License
         
        diff --git a/spi/build.gradle b/spi/build.gradle
        index 2cfe1a0d21..2e7c7edb87 100644
        --- a/spi/build.gradle
        +++ b/spi/build.gradle
        @@ -69,6 +69,13 @@ publishing {
                 }
             }
             repositories {
        -        mavenLocal()
        +        maven {
        +            name = "Snapshots" //  optional target repository name
        +            url = "https://aws.oss.sonatype.org/content/repositories/snapshots"
        +            credentials {
        +                username "$System.env.SONATYPE_USERNAME"
        +                password "$System.env.SONATYPE_PASSWORD"
        +            }
        +        }
             }
         }
        
        From e1e876f86e72f22aff01e6e210925e95d944c58a Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Tue, 14 Jan 2025 10:13:15 -0500
        Subject: [PATCH 083/122] Removes Guice reference from Sample plugin and
         changes warn log to info
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../sample/SampleResourcePlugin.java          | 48 -------------------
         .../security/OpenSearchSecurityPlugin.java    |  2 +-
         2 files changed, 1 insertion(+), 49 deletions(-)
        
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java
        index 6c68ef81ab..11cbbcb308 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java
        @@ -8,7 +8,6 @@
          */
         package org.opensearch.sample;
         
        -import java.util.ArrayList;
         import java.util.Collection;
         import java.util.Collections;
         import java.util.List;
        @@ -22,10 +21,6 @@
         import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
         import org.opensearch.cluster.node.DiscoveryNodes;
         import org.opensearch.cluster.service.ClusterService;
        -import org.opensearch.common.inject.Inject;
        -import org.opensearch.common.lifecycle.Lifecycle;
        -import org.opensearch.common.lifecycle.LifecycleComponent;
        -import org.opensearch.common.lifecycle.LifecycleListener;
         import org.opensearch.common.settings.ClusterSettings;
         import org.opensearch.common.settings.IndexScopedSettings;
         import org.opensearch.common.settings.Settings;
        @@ -50,7 +45,6 @@
         import org.opensearch.sample.resource.actions.transport.DeleteResourceTransportAction;
         import org.opensearch.script.ScriptService;
         import org.opensearch.security.spi.resources.ResourceParser;
        -import org.opensearch.security.spi.resources.ResourceService;
         import org.opensearch.security.spi.resources.ResourceSharingExtension;
         import org.opensearch.threadpool.ThreadPool;
         import org.opensearch.watcher.ResourceWatcherService;
        @@ -124,46 +118,4 @@ public String getResourceIndex() {
             public ResourceParser getResourceParser() {
                 return new SampleResourceParser();
             }
        -
        -    @Override
        -    public Collection> getGuiceServiceClasses() {
        -        final List> services = new ArrayList<>(1);
        -        services.add(GuiceHolder.class);
        -        return services;
        -    }
        -
        -    public static class GuiceHolder implements LifecycleComponent {
        -
        -        private static ResourceService resourceService;
        -
        -        @Inject
        -        public GuiceHolder(final ResourceService resourceService) {
        -            GuiceHolder.resourceService = resourceService;
        -        }
        -
        -        public static ResourceService getResourceService() {
        -            return resourceService;
        -        }
        -
        -        @Override
        -        public void close() {}
        -
        -        @Override
        -        public Lifecycle.State lifecycleState() {
        -            return null;
        -        }
        -
        -        @Override
        -        public void addLifecycleListener(LifecycleListener listener) {}
        -
        -        @Override
        -        public void removeLifecycleListener(LifecycleListener listener) {}
        -
        -        @Override
        -        public void start() {}
        -
        -        @Override
        -        public void stop() {}
        -
        -    }
         }
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index a65cc15f7d..96190f9f23 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -785,7 +785,7 @@ public void onIndexModule(IndexModule indexModule) {
                     resourceSharingIndexListener.initialize(threadPool, localClient, auditLog);
                     if (RESOURCE_INDICES.contains(indexModule.getIndex().getName())) {
                         indexModule.addIndexOperationListener(resourceSharingIndexListener);
        -                log.warn("Security plugin started listening to operations on resource-index {}", indexModule.getIndex().getName());
        +                log.info("Security plugin started listening to operations on resource-index {}", indexModule.getIndex().getName());
                     }
         
                     indexModule.forceQueryCacheProvider((indexSettings, nodeCache) -> new QueryCache() {
        
        From 45e09606ba0308c5ca1530fe05409b717064106b Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Tue, 14 Jan 2025 20:21:25 -0500
        Subject: [PATCH 084/122] Separates integration test dependencies and updates
         build.gradle files
        
        Signed-off-by: Darshit Chanpura 
        ---
         build.gradle                        |  78 +++++++++----
         sample-resource-plugin/build.gradle | 172 +++++++---------------------
         spi/build.gradle                    |   1 -
         3 files changed, 94 insertions(+), 157 deletions(-)
        
        diff --git a/build.gradle b/build.gradle
        index af07730ab5..b8c9288a60 100644
        --- a/build.gradle
        +++ b/build.gradle
        @@ -499,6 +499,10 @@ configurations {
                     force "org.checkerframework:checker-qual:3.48.4"
                     force "ch.qos.logback:logback-classic:1.5.16"
                     force "commons-io:commons-io:2.18.0"
        +            force "com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2"
        +            force "org.hamcrest:hamcrest:2.2"
        +            force "org.mockito:mockito-core:5.15.2"
        +            force "net.bytebuddy:byte-buddy:1.15.11"
                 }
             }
         
        @@ -506,6 +510,55 @@ configurations {
             integrationTestRuntimeOnly.extendsFrom runtimeOnly
         }
         
        +allprojects {
        +    configurations {
        +        integrationTestImplementation.extendsFrom implementation
        +    }
        +    dependencies {
        +        //integration test framework:
        +        integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') {
        +            exclude(group: 'junit', module: 'junit')
        +        }
        +        integrationTestImplementation 'junit:junit:4.13.2'
        +        integrationTestImplementation("org.opensearch.plugin:reindex-client:${opensearch_version}"){
        +            exclude(group: 'org.slf4j', module: 'slf4j-api')
        +        }
        +        integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}"
        +        integrationTestImplementation 'commons-io:commons-io:2.18.0'
        +        integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
        +        integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}"
        +        integrationTestImplementation 'org.hamcrest:hamcrest:2.2'
        +        integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}"
        +        integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}"
        +        integrationTestImplementation('org.awaitility:awaitility:4.2.2') {
        +            exclude(group: 'org.hamcrest', module: 'hamcrest')
        +        }
        +        integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14'
        +        integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}"
        +        integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14"
        +        integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14"
        +        integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14"
        +        integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16"
        +        integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5"
        +        integrationTestImplementation "org.mockito:mockito-core:5.15.2"
        +        integrationTestImplementation "org.passay:passay:1.6.6"
        +        integrationTestImplementation "org.opensearch:opensearch:${opensearch_version}"
        +        integrationTestImplementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}"
        +        integrationTestImplementation "org.opensearch.plugin:aggs-matrix-stats-client:${opensearch_version}"
        +        integrationTestImplementation "org.opensearch.plugin:parent-join-client:${opensearch_version}"
        +        integrationTestImplementation 'com.password4j:password4j:1.8.2'
        +        integrationTestImplementation "com.google.guava:guava:${guava_version}"
        +        integrationTestImplementation "org.apache.commons:commons-lang3:${versions.commonslang}"
        +        integrationTestImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
        +        integrationTestImplementation 'org.greenrobot:eventbus-java:3.3.1'
        +        integrationTestImplementation('com.flipkart.zjsonpatch:zjsonpatch:0.4.16'){
        +            exclude(group:'com.fasterxml.jackson.core')
        +        }
        +        integrationTestImplementation 'org.slf4j:slf4j-api:2.0.12'
        +        integrationTestImplementation 'com.selectivem.collections:special-collections-complete:1.4.0'
        +    }
        +}
        +
         //create source set 'integrationTest'
         //add classes from the main source set to the compilation and runtime classpaths of the integrationTest
         sourceSets {
        @@ -724,31 +777,6 @@ dependencies {
         
             compileOnly "org.opensearch:opensearch:${opensearch_version}"
         
        -    //integration test framework:
        -    integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') {
        -        exclude(group: 'junit', module: 'junit')
        -    }
        -    integrationTestImplementation 'junit:junit:4.13.2'
        -    integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}"
        -    integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}"
        -    integrationTestImplementation 'commons-io:commons-io:2.18.0'
        -    integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
        -    integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}"
        -    integrationTestImplementation 'org.hamcrest:hamcrest:2.2'
        -    integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}"
        -    integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}"
        -    integrationTestImplementation('org.awaitility:awaitility:4.2.2') {
        -        exclude(group: 'org.hamcrest', module: 'hamcrest')
        -    }
        -    integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14'
        -    integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}"
        -    integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14"
        -    integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14"
        -    integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14"
        -    integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16"
        -    integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5"
        -    integrationTestImplementation "org.mockito:mockito-core:5.15.2"
        -
             //spotless
             implementation('com.google.googlejavaformat:google-java-format:1.25.2') {
                 exclude group: 'com.google.guava'
        diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle
        index efdf700599..797fc78ac4 100644
        --- a/sample-resource-plugin/build.gradle
        +++ b/sample-resource-plugin/build.gradle
        @@ -5,9 +5,6 @@
         
         apply plugin: 'opensearch.opensearchplugin'
         apply plugin: 'opensearch.testclusters'
        -apply plugin: 'opensearch.java-rest-test'
        -
        -import org.opensearch.gradle.test.RestIntegTestTask
         
         java {
             sourceCompatibility = JavaVersion.VERSION_21
        @@ -20,8 +17,13 @@ opensearchplugin {
             classname 'org.opensearch.sample.SampleResourcePlugin'
         }
         
        +dependencyLicenses.enabled = false
        +thirdPartyAudit.enabled = false
        +loggerUsageCheck.enabled = false
        +tasks.test.enabled=false
        +validateNebulaPom.enabled=false
        +
         ext {
        -    projectSubstitutions = [:]
             licenseFile = rootProject.file('LICENSE.txt')
             noticeFile = rootProject.file('NOTICE.txt')
             opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT")
        @@ -31,7 +33,6 @@ ext {
             version_tokens = opensearch_version.tokenize('-')
             opensearch_build = version_tokens[0] + '.0'
         
        -
             if (buildVersionQualifier) {
                 opensearch_build += "-${buildVersionQualifier}"
             }
        @@ -46,144 +47,53 @@ repositories {
             maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
         }
         
        +configurations.all {
        +    resolutionStrategy {
        +        force 'com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2',
        +                'org.hamcrest:hamcrest:2.2',
        +                'org.apache.httpcomponents:httpclient:4.5.14',
        +                'org.apache.httpcomponents:httpcore:4.4.16',
        +                'org.mockito:mockito-core:5.15.2',
        +                'net.bytebuddy:byte-buddy:1.15.11',
        +                'commons-codec:commons-codec:1.16.1',
        +                'com.fasterxml.jackson.core:jackson-databind:2.18.2',
        +                'com.fasterxml.jackson.core:jackson-databind:2.18.2'
        +    }
        +}
        +
         dependencies {
        +    // Main implementation dependencies
             implementation "org.opensearch:opensearch-resource-sharing-spi:${opensearch_build}"
             implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
        -}
        -
        -dependencyLicenses.enabled = false
        -thirdPartyAudit.enabled = false
        -
        -def es_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile
        -es_tmp_dir.mkdirs()
        -
        -File repo = file("$buildDir/testclusters/repo")
        -def _numNodes = findProperty('numNodes') as Integer ?: 1
        -
        -licenseHeaders.enabled = true
        -validateNebulaPom.enabled = false
        -testingConventions.enabled = false
        -loggerUsageCheck.enabled = false
         
        -javaRestTest.dependsOn(rootProject.assemble)
        -javaRestTest {
        -    systemProperty 'tests.security.manager', 'false'
        +    // Integration test dependencies
        +    integrationTestImplementation rootProject.sourceSets.integrationTest.output
        +    integrationTestImplementation rootProject.sourceSets.main.output
         }
        -testClusters.javaRestTest {
        -    testDistribution = 'INTEG_TEST'
        -}
        -
        -task integTest(type: RestIntegTestTask) {
        -    description = "Run tests against a cluster"
        -    testClassesDirs = sourceSets.test.output.classesDirs
        -    classpath = sourceSets.test.runtimeClasspath
        -}
        -tasks.named("check").configure { dependsOn(integTest) }
        -
        -integTest {
        -    if (project.hasProperty('excludeTests')) {
        -        project.properties['excludeTests']?.replaceAll('\\s', '')?.split('[,;]')?.each {
        -            exclude "${it}"
        -        }
        -    }
        -    systemProperty 'tests.security.manager', 'false'
        -    systemProperty 'java.io.tmpdir', es_tmp_dir.absolutePath
        -
        -    systemProperty "https", System.getProperty("https")
        -    systemProperty "user", System.getProperty("user")
        -    systemProperty "password", System.getProperty("password")
        -    // Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for
        -    // requests. The 'doFirst' delays reading the debug setting on the cluster till execution time.
        -    doFirst {
        -        // Tell the test JVM if the cluster JVM is running under a debugger so that tests can
        -        // use longer timeouts for requests.
        -        def isDebuggingCluster = getDebug() || System.getProperty("test.debug") != null
        -        systemProperty 'cluster.debug', isDebuggingCluster
        -        // Set number of nodes system property to be used in tests
        -        systemProperty 'cluster.number_of_nodes', "${_numNodes}"
        -        // There seems to be an issue when running multi node run or integ tasks with unicast_hosts
        -        // not being written, the waitForAllConditions ensures it's written
        -        getClusters().forEach { cluster ->
        -            cluster.waitForAllConditions()
        -        }
        -    }
         
        -    // The -Dcluster.debug option makes the cluster debuggable; this makes the tests debuggable
        -    if (System.getProperty("test.debug") != null) {
        -        jvmArgs '-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=8000'
        -    }
        -    if (System.getProperty("tests.rest.bwcsuite") == null) {
        -        filter {
        -            excludeTestsMatching "org.opensearch.security.sampleextension.bwc.*IT"
        +sourceSets {
        +    integrationTest {
        +        java {
        +            srcDir file('src/integrationTest/java')
        +            compileClasspath += sourceSets.main.output
        +            runtimeClasspath += sourceSets.main.output
                 }
        -    }
        -}
        -project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build'))
        -Zip bundle = (Zip) project.getTasks().getByName("bundlePlugin");
        -Zip rootBundle = (Zip) rootProject.getTasks().getByName("bundlePlugin");
        -integTest.dependsOn(bundle)
        -integTest.getClusters().forEach{c -> {
        -    c.plugin(rootProject.getObjects().fileProperty().value(rootBundle.getArchiveFile()))
        -    c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile()))
        -}}
        -
        -testClusters.integTest {
        -    testDistribution = 'INTEG_TEST'
        -
        -    // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1
        -    if (_numNodes > 1) numberOfNodes = _numNodes
        -    // When running integration tests it doesn't forward the --debug-jvm to the cluster anymore
        -    // i.e. we have to use a custom property to flag when we want to debug OpenSearch JVM
        -    // since we also support multi node integration tests we increase debugPort per node
        -    if (System.getProperty("cluster.debug") != null) {
        -        def debugPort = 5005
        -        nodes.forEach { node ->
        -            node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=*:${debugPort}")
        -            debugPort += 1
        +        resources {
        +            srcDir file('src/integrationTest/resources')
                 }
             }
        -    setting 'path.repo', repo.absolutePath
         }
         
        -afterEvaluate {
        -    testClusters.integTest.nodes.each { node ->
        -        def plugins = node.plugins
        -        def firstPlugin = plugins.get(0)
        -        if (firstPlugin.provider == project.bundlePlugin.archiveFile) {
        -            plugins.remove(0)
        -            plugins.add(firstPlugin)
        -        }
        +tasks.register("integrationTest", Test) {
        +    description = 'Run integration tests for the subproject.'
        +    group = 'verification'
        +
        +    testClassesDirs = sourceSets.integrationTest.output.classesDirs
        +    classpath = sourceSets.integrationTest.runtimeClasspath
         
        -        node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem"))
        -        node.extraConfigFile("kirk-key.pem", file("src/test/resources/security/kirk-key.pem"))
        -        node.extraConfigFile("esnode.pem", file("src/test/resources/security/esnode.pem"))
        -        node.extraConfigFile("esnode-key.pem", file("src/test/resources/security/esnode-key.pem"))
        -        node.extraConfigFile("root-ca.pem", file("src/test/resources/security/root-ca.pem"))
        -        node.setting("plugins.security.ssl.transport.pemcert_filepath", "esnode.pem")
        -        node.setting("plugins.security.ssl.transport.pemkey_filepath", "esnode-key.pem")
        -        node.setting("plugins.security.ssl.transport.pemtrustedcas_filepath", "root-ca.pem")
        -        node.setting("plugins.security.ssl.transport.enforce_hostname_verification", "false")
        -        node.setting("plugins.security.ssl.http.enabled", "true")
        -        node.setting("plugins.security.ssl.http.pemcert_filepath", "esnode.pem")
        -        node.setting("plugins.security.ssl.http.pemkey_filepath", "esnode-key.pem")
        -        node.setting("plugins.security.ssl.http.pemtrustedcas_filepath", "root-ca.pem")
        -        node.setting("plugins.security.allow_unsafe_democertificates", "true")
        -        node.setting("plugins.security.allow_default_init_securityindex", "true")
        -        node.setting("plugins.security.authcz.admin_dn", "\n - CN=kirk,OU=client,O=client,L=test,C=de")
        -        node.setting("plugins.security.audit.type", "internal_opensearch")
        -        node.setting("plugins.security.enable_snapshot_restore_privilege", "true")
        -        node.setting("plugins.security.check_snapshot_restore_write_privileges", "true")
        -        node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]")
        -    }
         }
         
        -run {
        -    doFirst {
        -        // There seems to be an issue when running multi node run or integ tasks with unicast_hosts
        -        // not being written, the waitForAllConditions ensures it's written
        -        getClusters().forEach { cluster ->
        -            cluster.waitForAllConditions()
        -        }
        -    }
        -    useCluster testClusters.integTest
        +// Ensure integrationTest task depends on the root project's compile task
        +tasks.named("integrationTest").configure {
        +    dependsOn rootProject.tasks.named("compileIntegrationTestJava")
         }
        diff --git a/spi/build.gradle b/spi/build.gradle
        index 2e7c7edb87..b2db11979f 100644
        --- a/spi/build.gradle
        +++ b/spi/build.gradle
        @@ -20,7 +20,6 @@ repositories {
         
         dependencies {
             compileOnly "org.opensearch:opensearch:${opensearch_version}"
        -    testImplementation "org.opensearch.test:framework:${opensearch_version}"
         }
         
         java {
        
        From fb0f66fea703543a79bd4fb84bb4f024e830df22 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Tue, 14 Jan 2025 23:08:28 -0500
        Subject: [PATCH 085/122] Moves unconcerned changes to a new PR
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/DoNotFailOnForbiddenTests.java      | 16 ++++++----------
         .../support/SafeSerializationUtilsTest.java      |  6 ------
         2 files changed, 6 insertions(+), 16 deletions(-)
        
        diff --git a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java
        index 4aa6005beb..456d1ebada 100644
        --- a/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java
        +++ b/src/integrationTest/java/org/opensearch/security/DoNotFailOnForbiddenTests.java
        @@ -12,7 +12,6 @@
         import java.io.BufferedReader;
         import java.io.IOException;
         import java.io.InputStreamReader;
        -import java.nio.charset.StandardCharsets;
         import java.util.List;
         import java.util.stream.Collectors;
         
        @@ -463,9 +462,8 @@ public void shouldPerformCatIndices_positive() throws IOException {
                     Request getIndicesRequest = new Request("GET", "/_cat/indices");
                     // High level client doesn't support _cat/_indices API
                     Response getIndicesResponse = restHighLevelClient.getLowLevelClient().performRequest(getIndicesRequest);
        -            List indexes = new BufferedReader(
        -                new InputStreamReader(getIndicesResponse.getEntity().getContent(), StandardCharsets.UTF_8)
        -            ).lines().collect(Collectors.toList());
        +            List indexes = new BufferedReader(new InputStreamReader(getIndicesResponse.getEntity().getContent())).lines()
        +                .collect(Collectors.toList());
         
                     assertThat(indexes.size(), equalTo(1));
                     assertThat(indexes.get(0), containsString("marvelous_songs"));
        @@ -478,9 +476,8 @@ public void shouldPerformCatAliases_positive() throws IOException {
                 try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(LIMITED_USER)) {
                     Request getAliasesRequest = new Request("GET", "/_cat/aliases");
                     Response getAliasesResponse = restHighLevelClient.getLowLevelClient().performRequest(getAliasesRequest);
        -            List aliases = new BufferedReader(
        -                new InputStreamReader(getAliasesResponse.getEntity().getContent(), StandardCharsets.UTF_8)
        -            ).lines().collect(Collectors.toList());
        +            List aliases = new BufferedReader(new InputStreamReader(getAliasesResponse.getEntity().getContent())).lines()
        +                .collect(Collectors.toList());
         
                     // Does not fail on forbidden, but alias response only contains index which user has access to
                     assertThat(getAliasesResponse.getStatusLine().getStatusCode(), equalTo(200));
        @@ -493,9 +490,8 @@ public void shouldPerformCatAliases_positive() throws IOException {
                 try (RestHighLevelClient restHighLevelClient = cluster.getRestHighLevelClient(ADMIN_USER)) {
                     Request getAliasesRequest = new Request("GET", "/_cat/aliases");
                     Response getAliasesResponse = restHighLevelClient.getLowLevelClient().performRequest(getAliasesRequest);
        -            List aliases = new BufferedReader(
        -                new InputStreamReader(getAliasesResponse.getEntity().getContent(), StandardCharsets.UTF_8)
        -            ).lines().collect(Collectors.toList());
        +            List aliases = new BufferedReader(new InputStreamReader(getAliasesResponse.getEntity().getContent())).lines()
        +                .collect(Collectors.toList());
         
                     // Admin has access to all
                     assertThat(getAliasesResponse.getStatusLine().getStatusCode(), equalTo(200));
        diff --git a/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java b/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java
        index 187fd8b372..f69d4e0291 100644
        --- a/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java
        +++ b/src/test/java/org/opensearch/security/support/SafeSerializationUtilsTest.java
        @@ -17,7 +17,6 @@
         import java.util.HashMap;
         import java.util.regex.Pattern;
         
        -import org.junit.After;
         import org.junit.Test;
         
         import org.opensearch.security.auth.UserInjector;
        @@ -36,11 +35,6 @@
         
         public class SafeSerializationUtilsTest {
         
        -    @After
        -    public void clearCache() {
        -        SafeSerializationUtils.safeClassCache.clear();
        -    }
        -
             @Test
             public void testSafeClasses() {
                 assertTrue(SafeSerializationUtils.isSafeClass(String.class));
        
        From 4b8f236fab6c3307f01756c79bb6465996136e70 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Tue, 14 Jan 2025 23:25:46 -0500
        Subject: [PATCH 086/122] Fixes build.gradle and adds a new update APIs
        
        Signed-off-by: Darshit Chanpura 
        ---
         sample-resource-plugin/build.gradle                 | 13 +++++++++++--
         .../rest/create/CreateResourceRestAction.java       |  7 +++++--
         2 files changed, 16 insertions(+), 4 deletions(-)
        
        diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle
        index 797fc78ac4..99f8e4d74c 100644
        --- a/sample-resource-plugin/build.gradle
        +++ b/sample-resource-plugin/build.gradle
        @@ -15,15 +15,22 @@ opensearchplugin {
             name 'opensearch-sample-resource-plugin'
             description 'Sample plugin that extends OpenSearch Resource Plugin'
             classname 'org.opensearch.sample.SampleResourcePlugin'
        +    extendedPlugins = ['opensearch-security;optional=true']
         }
         
         dependencyLicenses.enabled = false
         thirdPartyAudit.enabled = false
         loggerUsageCheck.enabled = false
        -tasks.test.enabled=false
        -validateNebulaPom.enabled=false
        +validateNebulaPom.enabled = false
        +testingConventions.enabled = false
        +tasks.configureEach { task ->
        +    if(task.name.contains("forbiddenApisIntegrationTest")) {
        +        task.enabled = false
        +    }
        +}
         
         ext {
        +    projectSubstitutions = [:]
             licenseFile = rootProject.file('LICENSE.txt')
             noticeFile = rootProject.file('NOTICE.txt')
             opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT")
        @@ -97,3 +104,5 @@ tasks.register("integrationTest", Test) {
         tasks.named("integrationTest").configure {
             dependsOn rootProject.tasks.named("compileIntegrationTestJava")
         }
        +
        +project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build'))
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java
        index bcfa0ae9df..e990cc8a1d 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java
        @@ -19,8 +19,8 @@
         import org.opensearch.rest.action.RestToXContentListener;
         import org.opensearch.sample.SampleResource;
         
        -import static java.util.Collections.singletonList;
         import static org.opensearch.rest.RestRequest.Method.POST;
        +import static org.opensearch.rest.RestRequest.Method.PUT;
         
         public class CreateResourceRestAction extends BaseRestHandler {
         
        @@ -28,7 +28,10 @@ public CreateResourceRestAction() {}
         
             @Override
             public List routes() {
        -        return singletonList(new Route(POST, "/_plugins/sample_resource_sharing/create"));
        +        return List.of(
        +            new Route(PUT, "/_plugins/sample_resource_sharing/create"),
        +            new Route(POST, "/_plugins/sample_resource_sharing/update")
        +        );
             }
         
             @Override
        
        From 9f9dd0811572ed75520231f6d6a614afd1121ace Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Tue, 14 Jan 2025 23:26:31 -0500
        Subject: [PATCH 087/122] Cleans up SPI
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../ResourceAccessControlPlugin.java          |  21 --
         .../spi/resources/ResourceService.java        |  54 -----
         .../DefaultResourceAccessControlPlugin.java   |  28 ---
         .../spi/resources/fallback/package-info.java  |  14 --
         ...faultResourceAccessControlPluginTests.java | 123 ----------
         .../spi/resources/ResourceServiceTests.java   | 220 ------------------
         .../security/OpenSearchSecurityPlugin.java    |  12 +-
         7 files changed, 2 insertions(+), 470 deletions(-)
         delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java
         delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java
         delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java
         delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java
         delete mode 100644 spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java
         delete mode 100644 spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java
        
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java
        deleted file mode 100644
        index 5f9c2558c2..0000000000
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessControlPlugin.java
        +++ /dev/null
        @@ -1,21 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.security.spi.resources;
        -
        -/**
        - * This plugin allows to control access to resources. It is used by the ResourcePlugins to check whether a user has access to a resource defined by that plugin.
        - * It also defines java APIs to list, share or revoke resources with other users.
        - * User information will be fetched from the ThreadContext.
        - *
        - * @opensearch.experimental
        - */
        -public interface ResourceAccessControlPlugin {
        -
        -    boolean hasPermission(String resourceId, String resourceIndex, ResourceAccessScope> scope);
        -}
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java
        deleted file mode 100644
        index 19d24b97e6..0000000000
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceService.java
        +++ /dev/null
        @@ -1,54 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.security.spi.resources;
        -
        -import java.util.List;
        -import java.util.stream.Collectors;
        -
        -import org.apache.logging.log4j.LogManager;
        -import org.apache.logging.log4j.Logger;
        -
        -import org.opensearch.OpenSearchException;
        -import org.opensearch.common.inject.Inject;
        -import org.opensearch.security.spi.resources.fallback.DefaultResourceAccessControlPlugin;
        -
        -/**
        - * Service to get the current ResourceSharingExtension to perform authorization.
        - *
        - * @opensearch.experimental
        - */
        -public class ResourceService {
        -    private static final Logger log = LogManager.getLogger(ResourceService.class);
        -
        -    private final ResourceAccessControlPlugin resourceACPlugin;
        -
        -    @Inject
        -    public ResourceService(final List resourceACPlugins) {
        -
        -        if (resourceACPlugins.isEmpty()) {
        -            log.info("Security plugin disabled: Using DefaultResourceAccessControlPlugin");
        -            resourceACPlugin = new DefaultResourceAccessControlPlugin();
        -        } else if (resourceACPlugins.size() == 1) {
        -            log.info("Security plugin enabled: Using OpenSearchSecurityPlugin");
        -            resourceACPlugin = resourceACPlugins.get(0);
        -        } else {
        -            throw new OpenSearchException(
        -                "Multiple resource access control plugins are not supported, found: "
        -                    + resourceACPlugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(","))
        -            );
        -        }
        -    }
        -
        -    /**
        -     * Gets the ResourceAccessControlPlugin in-effect to perform authorization
        -     */
        -    public ResourceAccessControlPlugin getResourceAccessControlPlugin() {
        -        return resourceACPlugin;
        -    }
        -}
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java b/spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java
        deleted file mode 100644
        index 379aa15d5d..0000000000
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/fallback/DefaultResourceAccessControlPlugin.java
        +++ /dev/null
        @@ -1,28 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package org.opensearch.security.spi.resources.fallback;
        -
        -import org.opensearch.security.spi.resources.ResourceAccessControlPlugin;
        -import org.opensearch.security.spi.resources.ResourceAccessScope;
        -
        -/**
        - * A default plugin for resource access control
        - */
        -public class DefaultResourceAccessControlPlugin implements ResourceAccessControlPlugin {
        -    /**
        -     * @param resourceId    the resource on which access is to be checked
        -     * @param resourceIndex where the resource exists
        -     * @param scope         the scope being requested
        -     * @return true always since this is a passthrough implementation
        -     */
        -    @Override
        -    public boolean hasPermission(String resourceId, String resourceIndex, ResourceAccessScope> scope) {
        -        return true;
        -    }
        -}
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java b/spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java
        deleted file mode 100644
        index 2dd2803b38..0000000000
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/fallback/package-info.java
        +++ /dev/null
        @@ -1,14 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -/**
        - * This package defines a pass-through implementation of ResourceAccessControlPlugin.
        - *
        - * @opensearch.experimental
        - */
        -package main.java.org.opensearch.security.spi.resources.fallback;
        diff --git a/spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java b/spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java
        deleted file mode 100644
        index 686f8484b9..0000000000
        --- a/spi/src/tests/java/opensearch/security/spi/resources/DefaultResourceAccessControlPluginTests.java
        +++ /dev/null
        @@ -1,123 +0,0 @@
        -/*
        - * 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.
        - */
        -
        -package tests.java.opensearch.security.spi.resources;
        -
        -public class DefaultResourceAccessControlPluginTests {
        -    // @Override
        -    // protected Collection> nodePlugins() {
        -    // return List.of(TestResourcePlugin.class);
        -    // }
        -    //
        -    // public void testGetResources() throws IOException {
        -    // final Client client = client();
        -    //
        -    // createIndex(SAMPLE_TEST_INDEX);
        -    // indexSampleDocuments();
        -    //
        -    // Set resources;
        -    // try (
        -    // DefaultResourceAccessControlExtension plugin = new DefaultResourceAccessControlExtension(
        -    // client,
        -    // internalCluster().getInstance(ThreadPool.class)
        -    // )
        -    // ) {
        -    // resources = plugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, TestResourcePlugin.TestResource.class);
        -    //
        -    // assertNotNull(resources);
        -    // MatcherAssert.assertThat(resources, hasSize(2));
        -    //
        -    // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("1"))));
        -    // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2"))));
        -    // }
        -    // }
        -    //
        -    // public void testSampleResourcePluginListResources() throws IOException {
        -    // createIndex(SAMPLE_TEST_INDEX);
        -    // indexSampleDocuments();
        -    //
        -    // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin();
        -    // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class));
        -    //
        -    // Set resources = racPlugin.getAccessibleResourcesForCurrentUser(
        -    // SAMPLE_TEST_INDEX,
        -    // TestResourcePlugin.TestResource.class
        -    // );
        -    //
        -    // assertNotNull(resources);
        -    // MatcherAssert.assertThat(resources, hasSize(2));
        -    // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("1"))));
        -    // MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2"))));
        -    // }
        -    //
        -    // public void testSampleResourcePluginCallsHasPermission() {
        -    //
        -    // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin();
        -    // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class));
        -    //
        -    // boolean canAccess = racPlugin.hasPermission("1", SAMPLE_TEST_INDEX, null);
        -    //
        -    // MatcherAssert.assertThat(canAccess, is(true));
        -    //
        -    // }
        -    //
        -    // public void testSampleResourcePluginCallsShareWith() {
        -    //
        -    // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin();
        -    // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class));
        -    //
        -    // ResourceSharing sharingInfo = racPlugin.shareWith("1", SAMPLE_TEST_INDEX, new ShareWith(Set.of()));
        -    //
        -    // MatcherAssert.assertThat(sharingInfo, is(nullValue()));
        -    // }
        -    //
        -    // public void testSampleResourcePluginCallsRevokeAccess() {
        -    //
        -    // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin();
        -    // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class));
        -    //
        -    // ResourceSharing sharingInfo = racPlugin.revokeAccess("1", SAMPLE_TEST_INDEX, Map.of(), Set.of("some_scope"));
        -    //
        -    // MatcherAssert.assertThat(sharingInfo, is(nullValue()));
        -    // }
        -    //
        -    // public void testSampleResourcePluginCallsDeleteResourceSharingRecord() {
        -    // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin();
        -    // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class));
        -    //
        -    // boolean recordDeleted = racPlugin.deleteResourceSharingRecord("1", SAMPLE_TEST_INDEX);
        -    //
        -    // // no record to delete
        -    // MatcherAssert.assertThat(recordDeleted, is(false));
        -    // }
        -    //
        -    // public void testSampleResourcePluginCallsDeleteAllResourceSharingRecordsForCurrentUser() {
        -    // ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin();
        -    // MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlExtension.class));
        -    //
        -    // boolean recordDeleted = racPlugin.deleteAllResourceSharingRecordsForCurrentUser();
        -    //
        -    // // no records to delete
        -    // MatcherAssert.assertThat(recordDeleted, is(false));
        -    // }
        -    //
        -    // private void indexSampleDocuments() throws IOException {
        -    // XContentBuilder doc1 = jsonBuilder().startObject().field("id", "1").field("name", "Test Document 1").endObject();
        -    //
        -    // XContentBuilder doc2 = jsonBuilder().startObject().field("id", "2").field("name", "Test Document 2").endObject();
        -    //
        -    // try (Client client = client()) {
        -    //
        -    // client.prepareIndex(SAMPLE_TEST_INDEX).setId("1").setSource(doc1).get();
        -    //
        -    // client.prepareIndex(SAMPLE_TEST_INDEX).setId("2").setSource(doc2).get();
        -    //
        -    // client.admin().indices().prepareRefresh(SAMPLE_TEST_INDEX).get();
        -    // }
        -    // }
        -}
        diff --git a/spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java b/spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java
        deleted file mode 100644
        index e537dc1697..0000000000
        --- a/spi/src/tests/java/opensearch/security/spi/resources/ResourceServiceTests.java
        +++ /dev/null
        @@ -1,220 +0,0 @@
        -/// *
        -// * 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.
        -// */
        -//
        -// package tests.java.opensearch.security.spi.resources;
        -//
        -// import org.hamcrest.MatcherAssert;
        -// import org.mockito.Mock;
        -// import org.mockito.MockitoAnnotations;
        -// import org.opensearch.OpenSearchException;
        -// import org.opensearch.accesscontrol.resources.fallback.DefaultResourceAccessControlExtension;
        -// import org.opensearch.client.Client;
        -// import org.opensearch.plugins.ResourceAccessControlPlugin;
        -// import org.opensearch.plugins.ResourceSharingExtension;
        -// import org.opensearch.test.OpenSearchTestCase;
        -// import org.opensearch.threadpool.ThreadPool;
        -//
        -// import java.util.ArrayList;
        -// import java.util.Arrays;
        -// import java.util.Collections;
        -// import java.util.List;
        -//
        -// import static org.hamcrest.Matchers.*;
        -// import static org.mockito.Mockito.mock;
        -//
        -// public class ResourceServiceTests extends OpenSearchTestCase {
        -//
        -// @Mock
        -// private Client client;
        -//
        -// @Mock
        -// private ThreadPool threadPool;
        -//
        -// public void setup() {
        -// MockitoAnnotations.openMocks(this);
        -// }
        -//
        -// public void testGetResourceAccessControlPluginReturnsInitializedPlugin() {
        -// setup();
        -// Client mockClient = mock(Client.class);
        -// ThreadPool mockThreadPool = mock(ThreadPool.class);
        -//
        -// ResourceAccessControlPlugin mockPlugin = mock(ResourceAccessControlPlugin.class);
        -// List plugins = new ArrayList<>();
        -// plugins.add(mockPlugin);
        -//
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// ResourceService resourceService = new ResourceService(plugins, resourcePlugins, mockClient, mockThreadPool);
        -//
        -// ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin();
        -//
        -// MatcherAssert.assertThat(mockPlugin, equalTo(result));
        -// }
        -//
        -// public void testGetResourceAccessControlPlugin_NoPlugins() {
        -// setup();
        -// List emptyPlugins = new ArrayList<>();
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// ResourceService resourceService = new ResourceService(emptyPlugins, resourcePlugins, client, threadPool);
        -//
        -// ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin();
        -//
        -// assertNotNull(result);
        -// MatcherAssert.assertThat(result, instanceOf(DefaultResourceAccessControlExtension.class));
        -// }
        -//
        -// public void testGetResourceAccessControlPlugin_SinglePlugin() {
        -// setup();
        -// ResourceAccessControlPlugin mockPlugin = mock(ResourceAccessControlPlugin.class);
        -// List singlePlugin = Arrays.asList(mockPlugin);
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// ResourceService resourceService = new ResourceService(singlePlugin, resourcePlugins, client, threadPool);
        -//
        -// ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin();
        -//
        -// assertNotNull(result);
        -// assertSame(mockPlugin, result);
        -// }
        -//
        -// public void testListResourcePluginsReturnsPluginList() {
        -// setup();
        -// List resourceACPlugins = new ArrayList<>();
        -// List expectedResourcePlugins = new ArrayList<>();
        -// expectedResourcePlugins.add(mock(ResourceSharingExtension.class));
        -// expectedResourcePlugins.add(mock(ResourceSharingExtension.class));
        -//
        -// ResourceService resourceService = new ResourceService(resourceACPlugins, expectedResourcePlugins, client, threadPool);
        -//
        -// List actualResourcePlugins = resourceService.listResourcePlugins();
        -//
        -// MatcherAssert.assertThat(expectedResourcePlugins, equalTo(actualResourcePlugins));
        -// }
        -//
        -// public void testListResourcePlugins_concurrentModification() {
        -// setup();
        -// List emptyACPlugins = Collections.emptyList();
        -// List resourcePlugins = new ArrayList<>();
        -// resourcePlugins.add(mock(ResourceSharingExtension.class));
        -//
        -// ResourceService resourceService = new ResourceService(emptyACPlugins, resourcePlugins, client, threadPool);
        -//
        -// Thread modifierThread = new Thread(() -> { resourcePlugins.add(mock(ResourceSharingExtension.class)); });
        -//
        -// modifierThread.start();
        -//
        -// List result = resourceService.listResourcePlugins();
        -//
        -// assertNotNull(result);
        -// // The size could be either 1 or 2 depending on the timing of the concurrent modification
        -// assertTrue(result.size() == 1 || result.size() == 2);
        -// }
        -//
        -// public void testListResourcePlugins_emptyList() {
        -// setup();
        -// List emptyACPlugins = Collections.emptyList();
        -// List emptyResourcePlugins = Collections.emptyList();
        -//
        -// ResourceService resourceService = new ResourceService(emptyACPlugins, emptyResourcePlugins, client, threadPool);
        -//
        -// List result = resourceService.listResourcePlugins();
        -//
        -// assertNotNull(result);
        -// MatcherAssert.assertThat(result, is(empty()));
        -// }
        -//
        -// public void testListResourcePlugins_immutability() {
        -// setup();
        -// List emptyACPlugins = Collections.emptyList();
        -// List resourcePlugins = new ArrayList<>();
        -// resourcePlugins.add(mock(ResourceSharingExtension.class));
        -//
        -// ResourceService resourceService = new ResourceService(emptyACPlugins, resourcePlugins, client, threadPool);
        -//
        -// List result = resourceService.listResourcePlugins();
        -//
        -// assertThrows(UnsupportedOperationException.class, () -> { result.add(mock(ResourceSharingExtension.class)); });
        -// }
        -//
        -// public void testResourceServiceConstructorWithMultiplePlugins() {
        -// setup();
        -// ResourceAccessControlPlugin plugin1 = mock(ResourceAccessControlPlugin.class);
        -// ResourceAccessControlPlugin plugin2 = mock(ResourceAccessControlPlugin.class);
        -// List resourceACPlugins = Arrays.asList(plugin1, plugin2);
        -// List resourcePlugins = Arrays.asList(mock(ResourceSharingExtension.class));
        -//
        -// assertThrows(OpenSearchException.class, () -> { new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool); });
        -// }
        -//
        -// public void testResourceServiceConstructor_MultiplePlugins() {
        -// setup();
        -// ResourceAccessControlPlugin mockPlugin1 = mock(ResourceAccessControlPlugin.class);
        -// ResourceAccessControlPlugin mockPlugin2 = mock(ResourceAccessControlPlugin.class);
        -// List multiplePlugins = Arrays.asList(mockPlugin1, mockPlugin2);
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// assertThrows(
        -// org.opensearch.OpenSearchException.class,
        -// () -> { new ResourceService(multiplePlugins, resourcePlugins, client, threadPool); }
        -// );
        -// }
        -//
        -// public void testResourceServiceWithMultipleResourceACPlugins() {
        -// setup();
        -// List multipleResourceACPlugins = Arrays.asList(
        -// mock(ResourceAccessControlPlugin.class),
        -// mock(ResourceAccessControlPlugin.class)
        -// );
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// assertThrows(
        -// OpenSearchException.class,
        -// () -> { new ResourceService(multipleResourceACPlugins, resourcePlugins, client, threadPool); }
        -// );
        -// }
        -//
        -// public void testResourceServiceWithNoAccessControlPlugin() {
        -// setup();
        -// List resourceACPlugins = new ArrayList<>();
        -// List resourcePlugins = new ArrayList<>();
        -// Client client = mock(Client.class);
        -// ThreadPool threadPool = mock(ThreadPool.class);
        -//
        -// ResourceService resourceService = new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool);
        -//
        -// MatcherAssert.assertThat(resourceService.getResourceAccessControlPlugin(), instanceOf(DefaultResourceAccessControlExtension.class));
        -// MatcherAssert.assertThat(resourcePlugins, equalTo(resourceService.listResourcePlugins()));
        -// }
        -//
        -// public void testResourceServiceWithNoResourceACPlugins() {
        -// setup();
        -// List emptyResourceACPlugins = new ArrayList<>();
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// ResourceService resourceService = new ResourceService(emptyResourceACPlugins, resourcePlugins, client, threadPool);
        -//
        -// assertNotNull(resourceService.getResourceAccessControlPlugin());
        -// }
        -//
        -// public void testResourceServiceWithSingleResourceAccessControlPlugin() {
        -// setup();
        -// List resourceACPlugins = new ArrayList<>();
        -// ResourceAccessControlPlugin mockPlugin = mock(ResourceAccessControlPlugin.class);
        -// resourceACPlugins.add(mockPlugin);
        -//
        -// List resourcePlugins = new ArrayList<>();
        -//
        -// ResourceService resourceService = new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool);
        -//
        -// assertNotNull(resourceService);
        -// MatcherAssert.assertThat(mockPlugin, equalTo(resourceService.getResourceAccessControlPlugin()));
        -// MatcherAssert.assertThat(resourcePlugins, equalTo(resourceService.listResourcePlugins()));
        -// }
        -// }
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index 96190f9f23..72c78ef530 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -200,8 +200,6 @@
         import org.opensearch.security.setting.OpensearchDynamicSetting;
         import org.opensearch.security.setting.TransportPassiveAuthSetting;
         import org.opensearch.security.spi.resources.Resource;
        -import org.opensearch.security.spi.resources.ResourceAccessControlPlugin;
        -import org.opensearch.security.spi.resources.ResourceAccessScope;
         import org.opensearch.security.spi.resources.ResourceParser;
         import org.opensearch.security.spi.resources.ResourceProvider;
         import org.opensearch.security.spi.resources.ResourceSharingExtension;
        @@ -260,7 +258,6 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
                 ClusterPlugin,
                 MapperPlugin,
                 IdentityPlugin,
        -        ResourceAccessControlPlugin,
                 // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
                 ExtensionAwarePlugin,
                 ExtensiblePlugin
        @@ -301,7 +298,6 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
             private volatile DlsFlsBaseContext dlsFlsBaseContext;
             private ResourceSharingIndexManagementRepository rmr;
             private ResourceAccessHandler resourceAccessHandler;
        -    private final Set indicesToListen = new HashSet<>();
             private static final Map RESOURCE_PROVIDERS = new HashMap<>();
             private static final Set RESOURCE_INDICES = new HashSet<>();
         
        @@ -2306,15 +2302,11 @@ public static Set getResourceIndices() {
                 return ImmutableSet.copyOf(RESOURCE_INDICES);
             }
         
        -    @Override
        -    public boolean hasPermission(String resourceId, String resourceIndex, ResourceAccessScope> scope) {
        -        return this.resourceAccessHandler.hasPermission(resourceId, resourceIndex, scope.value());
        -    }
        -
             // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings
             @Override
             public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) {
         
        +        log.info("Loading extensions");
                 for (ResourceSharingExtension extension : loader.loadExtensions(ResourceSharingExtension.class)) {
                     String resourceType = extension.getResourceType();
                     String resourceIndexName = extension.getResourceIndex();
        @@ -2324,7 +2316,7 @@ public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) {
         
                     ResourceProvider resourceProvider = new ResourceProvider(resourceType, resourceIndexName, resourceParser);
                     RESOURCE_PROVIDERS.put(resourceIndexName, resourceProvider);
        -            log.info("Loaded resource provider extension: {}, index: {}", resourceType, resourceIndexName);
        +            log.info("Loaded resource sharing extension: {}, index: {}", resourceType, resourceIndexName);
                 }
             }
             // CS-ENFORCE-SINGLE
        
        From ac3ef42d489d1e6b173a525a11788feb81337973 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Wed, 15 Jan 2025 16:28:20 -0500
        Subject: [PATCH 088/122] Refactors build gradle and renames meta-inf services
         and updates the feature flag usage
        
        Signed-off-by: Darshit Chanpura 
        ---
         sample-resource-plugin/build.gradle           | 12 +++++--
         sample-resource-plugin/plugin-security.policy | 16 ++++++++++
         ...ty.spi.resources.ResourceSharingExtension} |  0
         .../security/OpenSearchSecurityPlugin.java    | 32 ++++++++++++-------
         .../resources/ResourceAccessHandler.java      |  7 ++--
         .../ResourceSharingIndexHandler.java          |  3 --
         .../security/support/ConfigConstants.java     |  2 +-
         7 files changed, 53 insertions(+), 19 deletions(-)
         create mode 100644 sample-resource-plugin/plugin-security.policy
         rename sample-resource-plugin/src/main/resources/META-INF/services/{org.opensearch.plugins.ResourcePlugin => org.opensearch.security.spi.resources.ResourceSharingExtension} (100%)
        
        diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle
        index 99f8e4d74c..e2152dfeb1 100644
        --- a/sample-resource-plugin/build.gradle
        +++ b/sample-resource-plugin/build.gradle
        @@ -70,12 +70,13 @@ configurations.all {
         
         dependencies {
             // Main implementation dependencies
        -    implementation "org.opensearch:opensearch-resource-sharing-spi:${opensearch_build}"
        -    implementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
        +    compileOnly "org.opensearch:opensearch-resource-sharing-spi:${opensearch_build}"
        +    compileOnly "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
         
             // Integration test dependencies
             integrationTestImplementation rootProject.sourceSets.integrationTest.output
             integrationTestImplementation rootProject.sourceSets.main.output
        +    integrationTestImplementation "org.opensearch:opensearch-resource-sharing-spi:${opensearch_build}"
         }
         
         sourceSets {
        @@ -91,6 +92,13 @@ sourceSets {
             }
         }
         
        +tasks.named("bundlePlugin") {
        +    from("$projectDir/plugin-security.policy") {
        +        into ''
        +    }
        +    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
        +}
        +
         tasks.register("integrationTest", Test) {
             description = 'Run integration tests for the subproject.'
             group = 'verification'
        diff --git a/sample-resource-plugin/plugin-security.policy b/sample-resource-plugin/plugin-security.policy
        new file mode 100644
        index 0000000000..9bb63a8402
        --- /dev/null
        +++ b/sample-resource-plugin/plugin-security.policy
        @@ -0,0 +1,16 @@
        + /*
        + * 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.
        + */
        +
        +grant {
        +  permission java.lang.RuntimePermission "getClassLoader";
        +  permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
        +  permission java.lang.RuntimePermission "accessDeclaredMembers";
        +};
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin b/sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.security.spi.resources.ResourceSharingExtension
        similarity index 100%
        rename from sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.plugins.ResourcePlugin
        rename to sample-resource-plugin/src/main/resources/META-INF/services/org.opensearch.security.spi.resources.ResourceSharingExtension
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index 72c78ef530..b7c6fce01b 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -779,7 +779,10 @@ public void onIndexModule(IndexModule indexModule) {
                     // Listening on POST and DELETE operations in resource indices
                     ResourceSharingIndexListener resourceSharingIndexListener = ResourceSharingIndexListener.getInstance();
                     resourceSharingIndexListener.initialize(threadPool, localClient, auditLog);
        -            if (RESOURCE_INDICES.contains(indexModule.getIndex().getName())) {
        +            if (settings.getAsBoolean(
        +                ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +                ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +            ) && RESOURCE_INDICES.contains(indexModule.getIndex().getName())) {
                         indexModule.addIndexOperationListener(resourceSharingIndexListener);
                         log.info("Security plugin started listening to operations on resource-index {}", indexModule.getIndex().getName());
                     }
        @@ -2184,7 +2187,10 @@ public void onNodeStarted(DiscoveryNode localNode) {
                 }
         
                 // rmr will be null when sec plugin is disabled or is in SSLOnly mode, hence rmr will not be instantiated
        -        if (rmr != null) {
        +        if (settings.getAsBoolean(
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +        ) && rmr != null) {
                     // create resource sharing index if absent
                     rmr.createResourceSharingIndexIfAbsent();
                 }
        @@ -2306,17 +2312,21 @@ public static Set getResourceIndices() {
             @Override
             public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) {
         
        -        log.info("Loading extensions");
        -        for (ResourceSharingExtension extension : loader.loadExtensions(ResourceSharingExtension.class)) {
        -            String resourceType = extension.getResourceType();
        -            String resourceIndexName = extension.getResourceIndex();
        -            ResourceParser resourceParser = extension.getResourceParser();
        +        if (settings.getAsBoolean(
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED,
        +            ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
        +        )) {
        +            for (ResourceSharingExtension extension : loader.loadExtensions(ResourceSharingExtension.class)) {
        +                String resourceType = extension.getResourceType();
        +                String resourceIndexName = extension.getResourceIndex();
        +                ResourceParser resourceParser = extension.getResourceParser();
         
        -            RESOURCE_INDICES.add(resourceIndexName);
        +                RESOURCE_INDICES.add(resourceIndexName);
         
        -            ResourceProvider resourceProvider = new ResourceProvider(resourceType, resourceIndexName, resourceParser);
        -            RESOURCE_PROVIDERS.put(resourceIndexName, resourceProvider);
        -            log.info("Loaded resource sharing extension: {}, index: {}", resourceType, resourceIndexName);
        +                ResourceProvider resourceProvider = new ResourceProvider(resourceType, resourceIndexName, resourceParser);
        +                RESOURCE_PROVIDERS.put(resourceIndexName, resourceProvider);
        +                log.info("Loaded resource sharing extension: {}, index: {}", resourceType, resourceIndexName);
        +            }
                 }
             }
             // CS-ENFORCE-SINGLE
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        index 47eaf65791..37a85dbcbd 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        @@ -275,7 +275,7 @@ private Set loadOwnResources(String resourceIndex, String userName) {
             }
         
             /**
        -     * Loads resources shared with the specified entities within the given resource index.
        +     * Loads resources shared with the specified entities within the given resource index, including public resources.
              *
              * @param resourceIndex The resource index to load resources from.
              * @param entities The set of entities to check for shared resources.
        @@ -283,7 +283,10 @@ private Set loadOwnResources(String resourceIndex, String userName) {
              * @return A set of resource IDs shared with the specified entities.
              */
             private Set loadSharedWithResources(String resourceIndex, Set entities, String RecipientType) {
        -        return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entities, RecipientType);
        +        Set entitiesCopy = new HashSet<>(entities);
        +        // To allow "public" resources to be matched for any user, role, backend_role
        +        entitiesCopy.add("*");
        +        return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entitiesCopy, RecipientType);
             }
         
             /**
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        index 1847a6f3d1..c339fa4d20 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        @@ -412,9 +412,6 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set
                     entities
                 );
         
        -        // To allow "public" resources to be matched for any user, role, backend_role
        -        entities.add("*");
        -
                 Set resourceIds = new HashSet<>();
                 final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
         
        diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java
        index dc0d68fea9..cd6fc011c0 100644
        --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java
        +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java
        @@ -381,7 +381,7 @@ public enum RolesMappingResolution {
             // Resource sharing index
             public static final String OPENSEARCH_RESOURCE_SHARING_INDEX = ".opensearch_resource_sharing";
             public static final String OPENSEARCH_RESOURCE_SHARING_ENABLED = SECURITY_SETTINGS_PREFIX + "resource_sharing.enabled";
        -    public static final boolean OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT = false;
        +    public static final boolean OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT = true;
         
             public static Set getSettingAsSet(
                 final Settings settings,
        
        From fcb5c9b9f7a6a98e7f9d6a0a30ed44d4b2789be3 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Thu, 16 Jan 2025 11:43:28 -0500
        Subject: [PATCH 089/122] Fetches the user from newly stored persistent header
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/resources/ResourceAccessHandler.java    | 12 ++++++------
         1 file changed, 6 insertions(+), 6 deletions(-)
        
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        index 37a85dbcbd..d1b19b7712 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        @@ -83,7 +83,7 @@ public void initializeRecipientTypes() {
              * @return A set of accessible resource IDs.
              */
             public Set getAccessibleResourceIdsForCurrentUser(String resourceIndex) {
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
        +        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
                 if (user == null) {
                     LOGGER.info("Unable to fetch user details ");
                     return Collections.emptySet();
        @@ -143,7 +143,7 @@ public  Set getAccessibleResourcesForCurrentUser(String r
             public boolean hasPermission(String resourceId, String resourceIndex, String scope) {
                 validateArguments(resourceId, resourceIndex, scope);
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
        +        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
         
                 LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId);
         
        @@ -184,7 +184,7 @@ public boolean hasPermission(String resourceId, String resourceIndex, String sco
             public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) {
                 validateArguments(resourceId, resourceIndex, shareWith);
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
        +        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
                 LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString());
         
                 // check if user is admin, if yes the user has permission
        @@ -208,7 +208,7 @@ public ResourceSharing revokeAccess(
                 Set scopes
             ) {
                 validateArguments(resourceId, resourceIndex, revokeAccess, scopes);
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
        +        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
                 LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes);
         
                 // check if user is admin, if yes the user has permission
        @@ -226,7 +226,7 @@ public ResourceSharing revokeAccess(
             public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) {
                 validateArguments(resourceId, resourceIndex);
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
        +        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
                 LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, resourceIndex, user.getName());
         
                 ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId);
        @@ -247,7 +247,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String resourceInd
              */
             public boolean deleteAllResourceSharingRecordsForCurrentUser() {
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER);
        +        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
                 LOGGER.info("Deleting all resource sharing records for resource {}", user.getName());
         
                 return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName());
        
        From 8bf699365c887bb2a4db0eaf521f3c1a78c36d99 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Thu, 16 Jan 2025 12:14:31 -0500
        Subject: [PATCH 090/122] Updates parser method to use XContentParser when
         contructing a resource from response
        
        Signed-off-by: Darshit Chanpura 
        ---
         sample-resource-plugin/build.gradle           |  7 ----
         sample-resource-plugin/plugin-security.policy | 16 ---------
         .../org/opensearch/sample/SampleResource.java | 33 +++++++++++++++++++
         .../sample/SampleResourceParser.java          | 23 ++-----------
         .../spi/resources/ResourceParser.java         |  8 +++--
         .../ResourceSharingIndexHandler.java          | 13 ++++++--
         6 files changed, 51 insertions(+), 49 deletions(-)
         delete mode 100644 sample-resource-plugin/plugin-security.policy
        
        diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle
        index e2152dfeb1..22c70d8389 100644
        --- a/sample-resource-plugin/build.gradle
        +++ b/sample-resource-plugin/build.gradle
        @@ -92,13 +92,6 @@ sourceSets {
             }
         }
         
        -tasks.named("bundlePlugin") {
        -    from("$projectDir/plugin-security.policy") {
        -        into ''
        -    }
        -    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
        -}
        -
         tasks.register("integrationTest", Test) {
             description = 'Run integration tests for the subproject.'
             group = 'verification'
        diff --git a/sample-resource-plugin/plugin-security.policy b/sample-resource-plugin/plugin-security.policy
        deleted file mode 100644
        index 9bb63a8402..0000000000
        --- a/sample-resource-plugin/plugin-security.policy
        +++ /dev/null
        @@ -1,16 +0,0 @@
        - /*
        - * 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.
        - */
        -
        -grant {
        -  permission java.lang.RuntimePermission "getClassLoader";
        -  permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
        -  permission java.lang.RuntimePermission "accessDeclaredMembers";
        -};
        \ No newline at end of file
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java
        index 508d8e7597..ce123380b4 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java
        @@ -14,11 +14,16 @@
         import java.io.IOException;
         import java.util.Map;
         
        +import org.opensearch.core.ParseField;
         import org.opensearch.core.common.io.stream.StreamInput;
         import org.opensearch.core.common.io.stream.StreamOutput;
        +import org.opensearch.core.xcontent.ConstructingObjectParser;
         import org.opensearch.core.xcontent.XContentBuilder;
        +import org.opensearch.core.xcontent.XContentParser;
         import org.opensearch.security.spi.resources.Resource;
         
        +import static org.opensearch.core.xcontent.ConstructingObjectParser.constructorArg;
        +
         public class SampleResource extends Resource {
         
             private String name;
        @@ -36,6 +41,34 @@ public SampleResource(StreamInput in) throws IOException {
                 this.attributes = in.readMap(StreamInput::readString, StreamInput::readString);
             }
         
        +    @SuppressWarnings("unchecked")
        +    private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(
        +        "sample_resource",
        +        true,
        +        a -> {
        +            SampleResource s;
        +            try {
        +                s = new SampleResource();
        +            } catch (IOException e) {
        +                throw new RuntimeException(e);
        +            }
        +            s.setName((String) a[0]);
        +            s.setDescription((String) a[1]);
        +            s.setAttributes((Map) a[2]);
        +            return s;
        +        }
        +    );
        +
        +    static {
        +        PARSER.declareString(constructorArg(), new ParseField("name"));
        +        PARSER.declareString(constructorArg(), new ParseField("description"));
        +        PARSER.declareObjectOrNull(constructorArg(), (p, c) -> p.mapStrings(), null, new ParseField("attributes"));
        +    }
        +
        +    public static SampleResource fromXContent(XContentParser parser) throws IOException {
        +        return PARSER.parse(parser, null);
        +    }
        +
             @Override
             public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
                 return builder.startObject().field("name", name).field("description", description).field("attributes", attributes).endObject();
        diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java
        index 4bb80fe0e4..42fb2582e2 100644
        --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java
        +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourceParser.java
        @@ -12,30 +12,13 @@
         package org.opensearch.sample;
         
         import java.io.IOException;
        -import java.security.AccessController;
        -import java.security.PrivilegedActionException;
        -import java.security.PrivilegedExceptionAction;
         
        -import com.fasterxml.jackson.databind.ObjectMapper;
        -
        -import org.opensearch.SpecialPermission;
        +import org.opensearch.core.xcontent.XContentParser;
         import org.opensearch.security.spi.resources.ResourceParser;
         
        -@SuppressWarnings("removal")
         public class SampleResourceParser implements ResourceParser {
             @Override
        -    public SampleResource parse(String s) throws IOException {
        -        ObjectMapper obj = new ObjectMapper();
        -        final SecurityManager sm = System.getSecurityManager();
        -
        -        if (sm != null) {
        -            sm.checkPermission(new SpecialPermission());
        -        }
        -
        -        try {
        -            return AccessController.doPrivileged((PrivilegedExceptionAction) () -> obj.readValue(s, SampleResource.class));
        -        } catch (final PrivilegedActionException e) {
        -            throw (IOException) e.getCause();
        -        }
        +    public SampleResource parseXContent(XContentParser parser) throws IOException {
        +        return SampleResource.fromXContent(parser);
             }
         }
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java
        index b3c2d0079d..be57200da4 100644
        --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java
        +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java
        @@ -10,12 +10,14 @@
         
         import java.io.IOException;
         
        +import org.opensearch.core.xcontent.XContentParser;
        +
         public interface ResourceParser {
             /**
        -     * Parse stringified json input to a desired Resource type
        -     * @param source the stringified json input
        +     * Parse source bytes supplied by the parser to a desired Resource type
        +     * @param parser to parser bytes-ref json input
              * @return the parsed object of Resource type
              * @throws IOException if something went wrong while parsing
              */
        -    T parse(String source) throws IOException;
        +    T parseXContent(XContentParser parser) throws IOException;
         }
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        index c339fa4d20..da8376244f 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        @@ -43,8 +43,10 @@
         import org.opensearch.common.unit.TimeValue;
         import org.opensearch.common.util.concurrent.ThreadContext;
         import org.opensearch.common.xcontent.LoggingDeprecationHandler;
        +import org.opensearch.common.xcontent.XContentHelper;
         import org.opensearch.common.xcontent.XContentType;
         import org.opensearch.core.action.ActionListener;
        +import org.opensearch.core.common.bytes.BytesReference;
         import org.opensearch.core.xcontent.NamedXContentRegistry;
         import org.opensearch.core.xcontent.ToXContent;
         import org.opensearch.core.xcontent.XContentBuilder;
        @@ -1187,9 +1189,14 @@ public  Set getResourceDocumentsFromIds(
         
                     for (MultiGetItemResponse itemResponse : response.getResponses()) {
                         if (!itemResponse.isFailed() && itemResponse.getResponse().isExists()) {
        -                    String sourceAsString = itemResponse.getResponse().getSourceAsString();
        -                    // T resource = DefaultObjectMapper.readValue(sourceAsString, clazz);
        -                    T resource = parser.parse(sourceAsString);
        +                    BytesReference sourceAsString = itemResponse.getResponse().getSourceAsBytesRef();
        +                    XContentParser xContentParser = XContentHelper.createParser(
        +                        NamedXContentRegistry.EMPTY,
        +                        LoggingDeprecationHandler.INSTANCE,
        +                        sourceAsString,
        +                        XContentType.JSON
        +                    );
        +                    T resource = parser.parseXContent(xContentParser);
                             result.add(resource);
                         }
                     }
        
        From 8534158a3978a6677a27efb6f4aae960bb8fd5ce Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Thu, 16 Jan 2025 15:32:24 -0500
        Subject: [PATCH 091/122] Revert :assemble change and updates build.gradle for
         sample-plugin
        
        Signed-off-by: Darshit Chanpura 
        ---
         .github/actions/create-bwc-build/action.yaml |  2 +-
         .github/workflows/ci.yml                     | 10 +++++-----
         .github/workflows/plugin_install.yml         |  2 +-
         sample-resource-plugin/build.gradle          |  2 --
         4 files changed, 7 insertions(+), 9 deletions(-)
        
        diff --git a/.github/actions/create-bwc-build/action.yaml b/.github/actions/create-bwc-build/action.yaml
        index 0f9e373b16..8960849333 100644
        --- a/.github/actions/create-bwc-build/action.yaml
        +++ b/.github/actions/create-bwc-build/action.yaml
        @@ -42,7 +42,7 @@ runs:
               uses: gradle/gradle-build-action@v2
               with:
                 cache-disabled: true
        -        arguments: :assemble
        +        arguments: assemble
                 build-root-directory: ${{ inputs.plugin-branch }}
         
             - id: get-opensearch-version
        diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
        index 9919075cc6..5a41062883 100644
        --- a/.github/workflows/ci.yml
        +++ b/.github/workflows/ci.yml
        @@ -208,7 +208,7 @@ jobs:
             - uses: github/codeql-action/init@v3
               with:
                 languages: java
        -    - run: ./gradlew clean :assemble
        +    - run: ./gradlew clean assemble
             - uses: github/codeql-action/analyze@v3
         
           build-artifact-names:
        @@ -238,13 +238,13 @@ jobs:
                 echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}
                 echo ${{ env.TEST_QUALIFIER }}
         
        -    - run: ./gradlew clean :assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip
        +    - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip
         
        -    - run: ./gradlew clean :assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip
        +    - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip
         
        -    - run: ./gradlew clean :assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip
        +    - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip
         
        -    - run: ./gradlew clean :assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip
        +    - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip
         
             - run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom
         
        diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml
        index c427b160c4..3f8d61795c 100644
        --- a/.github/workflows/plugin_install.yml
        +++ b/.github/workflows/plugin_install.yml
        @@ -32,7 +32,7 @@ jobs:
                 uses: gradle/gradle-build-action@v3
                 with:
                   cache-disabled: true
        -          arguments: :assemble
        +          arguments: assemble
         
               # Move and rename the plugin for installation
               - name: Move and rename the plugin for installation
        diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle
        index 22c70d8389..6ceca2704c 100644
        --- a/sample-resource-plugin/build.gradle
        +++ b/sample-resource-plugin/build.gradle
        @@ -105,5 +105,3 @@ tasks.register("integrationTest", Test) {
         tasks.named("integrationTest").configure {
             dependsOn rootProject.tasks.named("compileIntegrationTestJava")
         }
        -
        -project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build'))
        
        From 982612607401389f5cc88f2b2af7ebd98d75a546 Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 17 Jan 2025 16:05:54 -0500
        Subject: [PATCH 092/122] Adds a new type of exception for SPI
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../resources/ResourceSharingException.java   | 28 +++++++++++++++++++
         1 file changed, 28 insertions(+)
         create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingException.java
        
        diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingException.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingException.java
        new file mode 100644
        index 0000000000..e669341726
        --- /dev/null
        +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingException.java
        @@ -0,0 +1,28 @@
        +package org.opensearch.security.spi.resources;
        +
        +import java.io.IOException;
        +
        +import org.opensearch.OpenSearchException;
        +import org.opensearch.core.common.io.stream.StreamInput;
        +
        +/**
        + * This class represents an exception that occurs during resource sharing operations.
        + * It extends the OpenSearchException class.
        + */
        +public class ResourceSharingException extends OpenSearchException {
        +    public ResourceSharingException(Throwable cause) {
        +        super(cause);
        +    }
        +
        +    public ResourceSharingException(String msg, Object... args) {
        +        super(msg, args);
        +    }
        +
        +    public ResourceSharingException(String msg, Throwable cause, Object... args) {
        +        super(msg, cause, args);
        +    }
        +
        +    public ResourceSharingException(StreamInput in) throws IOException {
        +        super(in);
        +    }
        +}
        
        From fe0539270f136ba9e026107ffa0ebb56c90a975f Mon Sep 17 00:00:00 2001
        From: Darshit Chanpura 
        Date: Fri, 17 Jan 2025 16:06:34 -0500
        Subject: [PATCH 093/122] Changes actionGet calls to action listeners
        
        Signed-off-by: Darshit Chanpura 
        ---
         .../security/OpenSearchSecurityPlugin.java    |   7 +
         .../security/auth/UserSubjectImpl.java        |   4 +
         .../configuration/DlsFlsValveImpl.java        | 116 +--
         .../SecurityFlsDlsIndexSearcherWrapper.java   | 144 ++--
         .../security/resources/CreatedBy.java         |  16 +-
         .../resources/ResourceAccessHandler.java      | 360 ++++++---
         .../ResourceSharingIndexHandler.java          | 701 +++++++++++-------
         .../ResourceSharingIndexListener.java         |   2 +-
         .../RestListAccessibleResourcesAction.java    |  11 +-
         ...ransportListAccessibleResourcesAction.java |  23 +-
         .../TransportRevokeResourceAccessAction.java  |  39 +-
         .../access/TransportShareResourceAction.java  |  32 +-
         .../TransportVerifyResourceAccessAction.java  |  35 +-
         .../security/resources/CreatedByTests.java    |   4 +-
         14 files changed, 972 insertions(+), 522 deletions(-)
        
        diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        index 70fa1a6c3d..27340523d6 100644
        --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
        @@ -58,6 +58,7 @@
         import java.util.stream.Collectors;
         import java.util.stream.Stream;
         
        +import com.google.common.annotations.VisibleForTesting;
         import com.google.common.collect.ImmutableMap;
         import com.google.common.collect.ImmutableSet;
         import com.google.common.collect.Lists;
        @@ -2308,6 +2309,12 @@ public static Map getResourceProviders() {
                 return ImmutableMap.copyOf(RESOURCE_PROVIDERS);
             }
         
        +    // TODO following should be removed once core test framework allows loading extensions
        +    @VisibleForTesting
        +    public static Map getResourceProvidersMutable() {
        +        return RESOURCE_PROVIDERS;
        +    }
        +
             public static Set getResourceIndices() {
                 return ImmutableSet.copyOf(RESOURCE_INDICES);
             }
        diff --git a/src/main/java/org/opensearch/security/auth/UserSubjectImpl.java b/src/main/java/org/opensearch/security/auth/UserSubjectImpl.java
        index 63adc559e3..a28ed8dd63 100644
        --- a/src/main/java/org/opensearch/security/auth/UserSubjectImpl.java
        +++ b/src/main/java/org/opensearch/security/auth/UserSubjectImpl.java
        @@ -48,4 +48,8 @@ public  T runAs(Callable callable) throws Exception {
                     return callable.call();
                 }
             }
        +
        +    public User getUser() {
        +        return user;
        +    }
         }
        diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        index 22a05edcd0..b776284af5 100644
        --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java
        @@ -17,7 +17,6 @@
         import java.util.Comparator;
         import java.util.List;
         import java.util.Objects;
        -import java.util.Set;
         import java.util.concurrent.atomic.AtomicReference;
         import java.util.function.Consumer;
         import java.util.stream.StreamSupport;
        @@ -392,61 +391,71 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo
         
                     DlsFlsProcessedConfig config = this.dlsFlsProcessedConfig.get();
         
        -            DlsRestriction dlsRestriction;
        -
        -            Set resourceIds;
                     if (this.isResourceSharingEnabled && OpenSearchSecurityPlugin.getResourceIndices().contains(index)) {
        -                resourceIds = this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(index);
        -                // Create a DLS restriction to filter search results with accessible resources only
        -                dlsRestriction = this.resourceAccessHandler.createResourceDLSRestriction(resourceIds, namedXContentRegistry);
        +                this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(index, ActionListener.wrap(resourceIds -> {
         
        +                    log.info("Creating a DLS restriction for resource IDs: {}", resourceIds);
        +                    // Create a DLS restriction to filter search results with accessible resources only
        +                    DlsRestriction dlsRestriction = this.resourceAccessHandler.createResourceDLSRestriction(
        +                        resourceIds,
        +                        namedXContentRegistry
        +                    );
        +                    applyDlsRestrictionToSearchContext(dlsRestriction, index, searchContext, mode);
        +                }, exception -> {
        +                    log.error("Failed to fetch resource IDs for index '{}': {}", index, exception.getMessage());
        +                    applyDlsRestrictionToSearchContext(DlsRestriction.FULL, index, searchContext, mode);
        +                }));
                     } else {
        -                dlsRestriction = config.getDocumentPrivileges().getRestriction(privilegesEvaluationContext, index);
        +                DlsRestriction dlsRestriction = config.getDocumentPrivileges().getRestriction(privilegesEvaluationContext, index);
        +                applyDlsRestrictionToSearchContext(dlsRestriction, index, searchContext, mode);
                     }
         
        -            if (log.isTraceEnabled()) {
        -                log.trace("handleSearchContext(); index: {}; dlsRestriction: {}", index, dlsRestriction);
        -            }
        +        } catch (Exception e) {
        +            log.error("Error in handleSearchContext()", e);
        +            throw new RuntimeException("Error evaluating dls for a search query: " + e, e);
        +        }
        +    }
         
        -            DocumentAllowList documentAllowList = DocumentAllowList.get(threadContext);
        +    private void applyDlsRestrictionToSearchContext(DlsRestriction dlsRestriction, String index, SearchContext searchContext, Mode mode) {
        +        if (log.isTraceEnabled()) {
        +            log.trace("handleSearchContext(); index: {}; dlsRestriction: {}", index, dlsRestriction);
        +        }
         
        -            if (documentAllowList.isEntryForIndexPresent(index)) {
        -                // The documentAllowList is needed for two cases:
        -                // - DLS rules which use "term lookup queries" and thus need to access indices for which no privileges are present
        -                // - Dashboards multi tenancy which can redirect index accesses to indices for which no normal index privileges are present
        +        DocumentAllowList documentAllowList = DocumentAllowList.get(threadContext);
         
        -                if (!dlsRestriction.isUnrestricted() && documentAllowList.isAllowed(index, "*")) {
        -                    dlsRestriction = DlsRestriction.NONE;
        -                    log.debug("Lifting DLS for {} due to present document allowlist", index);
        -                }
        +        if (documentAllowList.isEntryForIndexPresent(index)) {
        +            // The documentAllowList is needed for two cases:
        +            // - DLS rules which use "term lookup queries" and thus need to access indices for which no privileges are present
        +            // - Dashboards multi tenancy which can redirect index accesses to indices for which no normal index privileges are present
        +
        +            if (!dlsRestriction.isUnrestricted() && documentAllowList.isAllowed(index, "*")) {
        +                dlsRestriction = DlsRestriction.NONE;
        +                log.debug("Lifting DLS for {} due to present document allowlist", index);
                     }
        +        }
         
        -            if (!dlsRestriction.isUnrestricted()) {
        -                if (mode == Mode.ADAPTIVE && dlsRestriction.containsTermLookupQuery()) {
        -                    // Special case for scroll operations:
        -                    // Normally, the check dlsFlsBaseContext.isDlsDoneOnFilterLevel() already aborts early if DLS filter level mode
        -                    // has been activated. However, this is not the case for scroll operations, as these lose the thread context value
        -                    // on which dlsFlsBaseContext.isDlsDoneOnFilterLevel() is based on. Thus, we need to check here again the deeper
        -                    // conditions.
        -                    log.trace("DlsRestriction: contains TLQ.");
        -                    return;
        -                }
        +        if (!dlsRestriction.isUnrestricted()) {
        +            if (mode == Mode.ADAPTIVE && dlsRestriction.containsTermLookupQuery()) {
        +                // Special case for scroll operations:
        +                // Normally, the check dlsFlsBaseContext.isDlsDoneOnFilterLevel() already aborts early if DLS filter level mode
        +                // has been activated. However, this is not the case for scroll operations, as these lose the thread context value
        +                // on which dlsFlsBaseContext.isDlsDoneOnFilterLevel() is based on. Thus, we need to check here again the deeper
        +                // conditions.
        +                log.trace("DlsRestriction: contains TLQ.");
        +                return;
        +            }
         
        -                assert searchContext.parsedQuery() != null;
        +            assert searchContext.parsedQuery() != null;
         
        -                BooleanQuery.Builder queryBuilder = dlsRestriction.toBooleanQueryBuilder(
        -                    searchContext.getQueryShardContext(),
        -                    (q) -> new ConstantScoreQuery(q)
        -                );
        +            BooleanQuery.Builder queryBuilder = dlsRestriction.toBooleanQueryBuilder(
        +                searchContext.getQueryShardContext(),
        +                (q) -> new ConstantScoreQuery(q)
        +            );
         
        -                queryBuilder.add(searchContext.parsedQuery().query(), Occur.MUST);
        +            queryBuilder.add(searchContext.parsedQuery().query(), Occur.MUST);
         
        -                searchContext.parsedQuery(new ParsedQuery(queryBuilder.build()));
        -                searchContext.preProcess(true);
        -            }
        -        } catch (Exception e) {
        -            log.error("Error in handleSearchContext()", e);
        -            throw new RuntimeException("Error evaluating dls for a search query: " + e, e);
        +            searchContext.parsedQuery(new ParsedQuery(queryBuilder.build()));
        +            searchContext.preProcess(true);
                 }
             }
         
        @@ -515,10 +524,7 @@ private static InternalAggregation aggregateBuckets(InternalAggregation aggregat
                 return aggregation;
             }
         
        -    private static List mergeBuckets(
        -        List buckets,
        -        Comparator comparator
        -    ) {
        +    private static List mergeBuckets(List buckets, Comparator comparator) {
                 if (log.isDebugEnabled()) {
                     log.debug("Merging buckets: {}", buckets.stream().map(b -> b.getKeyAsString()).collect(ImmutableList.toImmutableList()));
                 }
        @@ -562,12 +568,12 @@ private Mode getDlsModeHeader() {
         
             private static class BucketMerger implements Consumer {
                 private Comparator comparator;
        -        private StringTerms.Bucket bucket = null;
        +        private Bucket bucket = null;
                 private int mergeCount;
                 private long mergedDocCount;
                 private long mergedDocCountError;
                 private boolean showDocCountError = true;
        -        private final ImmutableList.Builder builder;
        +        private final ImmutableList.Builder builder;
         
                 BucketMerger(Comparator comparator, int size) {
                     this.comparator = Objects.requireNonNull(comparator);
        @@ -579,7 +585,7 @@ private void finalizeBucket() {
                         builder.add(this.bucket);
                     } else {
                         builder.add(
        -                    new StringTerms.Bucket(
        +                    new Bucket(
                                 StringTermsGetter.getTerm(bucket),
                                 mergedDocCount,
                                 (InternalAggregations) bucket.getAggregations(),
        @@ -591,7 +597,7 @@ private void finalizeBucket() {
                     }
                 }
         
        -        private void merge(StringTerms.Bucket bucket) {
        +        private void merge(Bucket bucket) {
                     if (this.bucket != null && (bucket == null || comparator.compare(this.bucket, bucket) != 0)) {
                         finalizeBucket();
                         this.bucket = null;
        @@ -602,13 +608,13 @@ private void merge(StringTerms.Bucket bucket) {
                     }
                 }
         
        -        public List getBuckets() {
        +        public List getBuckets() {
                     merge(null);
                     return builder.build();
                 }
         
                 @Override
        -        public void accept(StringTerms.Bucket bucket) {
        +        public void accept(Bucket bucket) {
                     merge(bucket);
                     mergeCount++;
                     mergedDocCount += bucket.getDocCount();
        @@ -625,7 +631,7 @@ public void accept(StringTerms.Bucket bucket) {
         
             private static class StringTermsGetter {
                 private static final Field REDUCE_ORDER = getField(InternalTerms.class, "reduceOrder");
        -        private static final Field TERM_BYTES = getField(StringTerms.Bucket.class, "termBytes");
        +        private static final Field TERM_BYTES = getField(Bucket.class, "termBytes");
                 private static final Field FORMAT = getField(InternalTerms.Bucket.class, "format");
         
                 private StringTermsGetter() {}
        @@ -669,11 +675,11 @@ public static BucketOrder getReduceOrder(StringTerms stringTerms) {
                     return getFieldValue(REDUCE_ORDER, stringTerms);
                 }
         
        -        public static BytesRef getTerm(StringTerms.Bucket bucket) {
        +        public static BytesRef getTerm(Bucket bucket) {
                     return getFieldValue(TERM_BYTES, bucket);
                 }
         
        -        public static DocValueFormat getDocValueFormat(StringTerms.Bucket bucket) {
        +        public static DocValueFormat getDocValueFormat(Bucket bucket) {
                     return getFieldValue(FORMAT, bucket);
                 }
             }
        diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        index 662476928d..af0a1a9282 100644
        --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java
        @@ -16,6 +16,8 @@
         import java.util.Collections;
         import java.util.HashSet;
         import java.util.Set;
        +import java.util.concurrent.CountDownLatch;
        +import java.util.concurrent.atomic.AtomicReference;
         import java.util.function.LongSupplier;
         import java.util.function.Supplier;
         
        @@ -29,6 +31,7 @@
         import org.opensearch.cluster.metadata.IndexMetadata;
         import org.opensearch.cluster.service.ClusterService;
         import org.opensearch.common.settings.Settings;
        +import org.opensearch.core.action.ActionListener;
         import org.opensearch.core.common.Strings;
         import org.opensearch.core.index.shard.ShardId;
         import org.opensearch.index.IndexService;
        @@ -48,6 +51,7 @@
         import org.opensearch.security.privileges.dlsfls.FieldMasking;
         import org.opensearch.security.privileges.dlsfls.FieldPrivileges;
         import org.opensearch.security.resources.ResourceAccessHandler;
        +import org.opensearch.security.spi.resources.ResourceSharingException;
         import org.opensearch.security.support.ConfigConstants;
         
         public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapper {
        @@ -119,60 +123,113 @@ public SecurityFlsDlsIndexSearcherWrapper(
             @SuppressWarnings("unchecked")
             @Override
             protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdmin) throws IOException {
        -
                 final ShardId shardId = ShardUtils.extractShardId(reader);
                 PrivilegesEvaluationContext privilegesEvaluationContext = this.dlsFlsBaseContext.getPrivilegesEvaluationContext();
        +        final String indexName = (shardId != null) ? shardId.getIndexName() : null;
         
                 if (log.isTraceEnabled()) {
        -            log.trace("dlsFlsWrap(); index: {}; privilegeEvaluationContext: {}", index.getName(), privilegesEvaluationContext);
        +            log.trace("dlsFlsWrap(); index: {}; isAdmin: {}", indexName, isAdmin);
                 }
         
        -        String indexName = shardId != null ? shardId.getIndexName() : null;
        -        Set resourceIds;
        -        if (this.isResourceSharingEnabled
        -            && !Strings.isNullOrEmpty(indexName)
        -            && OpenSearchSecurityPlugin.getResourceIndices().contains(indexName)) {
        -            resourceIds = this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(indexName);
        -            // resourceIds.isEmpty() indicates that the index is a resource index but the user does not have access to any resource under
        -            // the
        -            // index
        -            if (resourceIds.isEmpty()) {
        -                return new EmptyFilterLeafReader.EmptyDirectoryReader(reader);
        -            }
        -            // Create a resource DLS query for the current user
        -            QueryShardContext queryShardContext = this.indexService.newQueryShardContext(shardId.getId(), null, nowInMillis, null);
        -            Query resourceQuery = this.resourceAccessHandler.createResourceDLSQuery(resourceIds, queryShardContext);
        +        // 1. If user is admin, or we have no shard/index info, just wrap with default logic (no doc-level restriction).
        +        if (isAdmin || Strings.isNullOrEmpty(indexName)) {
        +            return wrapWithDefaultDlsFls(reader, shardId);
        +        }
         
        -            // TODO the FlsRule must still be checked
        -            return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(
        -                reader,
        -                FieldPrivileges.FlsRule.ALLOW_ALL,
        -                resourceQuery,
        -                indexService,
        -                threadContext,
        -                clusterService,
        -                auditlog,
        -                FieldMasking.FieldMaskingRule.ALLOW_ALL,
        -                shardId,
        -                metaFields
        -            );
        +        // 2. If resource sharing is disabled or this is not a resource index, fallback to standard DLS/FLS logic.
        +        if (!this.isResourceSharingEnabled || !OpenSearchSecurityPlugin.getResourceIndices().contains(indexName)) {
        +            return wrapStandardDlsFls(privilegesEvaluationContext, reader, shardId, indexName, isAdmin);
                 }
         
        -        if (isAdmin || privilegesEvaluationContext == null) {
        -            return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(
        -                reader,
        -                FieldPrivileges.FlsRule.ALLOW_ALL,
        -                null,
        -                indexService,
        -                threadContext,
        -                clusterService,
        -                auditlog,
        -                FieldMasking.FieldMaskingRule.ALLOW_ALL,
        -                shardId,
        -                metaFields
        -            );
        +        // TODO see if steps 3,4,5 can be changed to be completely asynchronous
        +        // 3.Since we need DirectoryReader *now*, we'll block the thread using a CountDownLatch until the async call completes.
        +        final AtomicReference> resourceIdsRef = new AtomicReference<>(Collections.emptySet());
        +        final AtomicReference exceptionRef = new AtomicReference<>(null);
        +        final CountDownLatch latch = new CountDownLatch(1);
        +
        +        // 4. Perform the async call to fetch resource IDs
        +        this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(indexName, ActionListener.wrap(resourceIds -> {
        +            log.debug("Fetched resource IDs for index '{}': {}", indexName, resourceIds);
        +            resourceIdsRef.set(resourceIds);
        +            latch.countDown();
        +        }, ex -> {
        +            log.error("Failed to fetch resource IDs for index '{}': {}", indexName, ex.getMessage(), ex);
        +            exceptionRef.set(ex);
        +            latch.countDown();
        +        }));
        +
        +        // 5. Block until the async call completes
        +        try {
        +            latch.await();
        +        } catch (InterruptedException e) {
        +            Thread.currentThread().interrupt();
        +            throw new IOException("Interrupted while waiting for resource IDs", e);
        +        }
        +
        +        // 6. Throw any errors
        +        if (exceptionRef.get() != null) {
        +            throw new ResourceSharingException("Failed to get resource IDs for index: " + indexName, exceptionRef.get());
        +        }
        +
        +        // 7. If the user has no accessible resources, produce a reader that yields zero documents
        +        final Set resourceIds = resourceIdsRef.get();
        +        if (resourceIds.isEmpty()) {
        +            log.debug("User has no accessible resources in index '{}'; returning EmptyDirectoryReader.", indexName);
        +            return new EmptyFilterLeafReader.EmptyDirectoryReader(reader);
                 }
         
        +        // 8. Build the resource-based query to restrict docs
        +        final QueryShardContext queryShardContext = this.indexService.newQueryShardContext(shardId.getId(), null, nowInMillis, null);
        +        final Query resourceQuery = this.resourceAccessHandler.createResourceDLSQuery(resourceIds, queryShardContext);
        +
        +        log.debug("Applying resource-based DLS query for index '{}'", indexName);
        +
        +        // 9. Wrap with a DLS/FLS DirectoryReader that includes doc-level restriction (resourceQuery),
        +        // with FLS (ALLOW_ALL) since we don't need field-level restrictions here.
        +        return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(
        +            reader,
        +            FieldPrivileges.FlsRule.ALLOW_ALL,
        +            resourceQuery,
        +            indexService,
        +            threadContext,
        +            clusterService,
        +            auditlog,
        +            FieldMasking.FieldMaskingRule.ALLOW_ALL,
        +            shardId,
        +            metaFields
        +        );
        +    }
        +
        +    /**
        +     * Wrap the reader with an "ALLOW_ALL" doc-level filter and field privileges,
        +     * i.e., no doc-level or field-level restrictions.
        +     */
        +    private DirectoryReader wrapWithDefaultDlsFls(DirectoryReader reader, ShardId shardId) throws IOException {
        +        return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(
        +            reader,
        +            FieldPrivileges.FlsRule.ALLOW_ALL,
        +            null,  // no doc-level restriction
        +            indexService,
        +            threadContext,
        +            clusterService,
        +            auditlog,
        +            FieldMasking.FieldMaskingRule.ALLOW_ALL,
        +            shardId,
        +            metaFields
        +        );
        +    }
        +
        +    /**
        +     * Fallback to your existing logic to handle DLS/FLS if the index is not a resource index,
        +     * or if other conditions apply (like dlsFlsBaseContext usage, etc.).
        +     */
        +    private DirectoryReader wrapStandardDlsFls(
        +        PrivilegesEvaluationContext privilegesEvaluationContext,
        +        DirectoryReader reader,
        +        ShardId shardId,
        +        String indexName,
        +        boolean isAdmin
        +    ) throws IOException {
                 try {
                     DlsFlsProcessedConfig config = this.dlsFlsProcessedConfigSupplier.get();
                     DlsRestriction dlsRestriction;
        @@ -244,4 +301,5 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm
                     throw new OpenSearchException("Error while evaluating DLS/FLS", e);
                 }
             }
        +
         }
        diff --git a/src/main/java/org/opensearch/security/resources/CreatedBy.java b/src/main/java/org/opensearch/security/resources/CreatedBy.java
        index 3790d56a72..69af99719e 100644
        --- a/src/main/java/org/opensearch/security/resources/CreatedBy.java
        +++ b/src/main/java/org/opensearch/security/resources/CreatedBy.java
        @@ -25,16 +25,16 @@
          */
         public class CreatedBy implements ToXContentFragment, NamedWriteable {
         
        -    private final String creatorType;
        +    private final Enum creatorType;
             private final String creator;
         
        -    public CreatedBy(String creatorType, String creator) {
        +    public CreatedBy(Enum creatorType, String creator) {
                 this.creatorType = creatorType;
                 this.creator = creator;
             }
         
             public CreatedBy(StreamInput in) throws IOException {
        -        this.creatorType = in.readString();
        +        this.creatorType = in.readEnum(Creator.class);
                 this.creator = in.readString();
             }
         
        @@ -42,7 +42,7 @@ public String getCreator() {
                 return creator;
             }
         
        -    public String getCreatorType() {
        +    public Enum getCreatorType() {
                 return creatorType;
             }
         
        @@ -58,23 +58,23 @@ public String getWriteableName() {
         
             @Override
             public void writeTo(StreamOutput out) throws IOException {
        -        out.writeString(creatorType);
        +        out.writeEnum(Creator.valueOf(creatorType.name()));
                 out.writeString(creator);
             }
         
             @Override
             public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
        -        return builder.startObject().field(creatorType, creator).endObject();
        +        return builder.startObject().field(String.valueOf(creatorType), creator).endObject();
             }
         
             public static CreatedBy fromXContent(XContentParser parser) throws IOException {
                 String creator = null;
        -        String creatorType = null;
        +        Enum creatorType = null;
                 XContentParser.Token token;
         
                 while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
                     if (token == XContentParser.Token.FIELD_NAME) {
        -                creatorType = parser.currentName();
        +                creatorType = Creator.valueOf(parser.currentName());
                     } else if (token == XContentParser.Token.VALUE_STRING) {
                         creator = parser.text();
                     }
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        index d1b19b7712..b1387e712c 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java
        @@ -23,7 +23,10 @@
         import org.apache.logging.log4j.Logger;
         import org.apache.lucene.search.Query;
         
        +import org.opensearch.OpenSearchException;
        +import org.opensearch.action.StepListener;
         import org.opensearch.common.util.concurrent.ThreadContext;
        +import org.opensearch.core.action.ActionListener;
         import org.opensearch.core.xcontent.NamedXContentRegistry;
         import org.opensearch.index.query.BoolQueryBuilder;
         import org.opensearch.index.query.ConstantScoreQueryBuilder;
        @@ -32,12 +35,14 @@
         import org.opensearch.index.query.QueryShardContext;
         import org.opensearch.security.DefaultObjectMapper;
         import org.opensearch.security.OpenSearchSecurityPlugin;
        +import org.opensearch.security.auth.UserSubjectImpl;
         import org.opensearch.security.configuration.AdminDNs;
         import org.opensearch.security.privileges.PrivilegesConfigurationValidationException;
         import org.opensearch.security.privileges.dlsfls.DlsRestriction;
         import org.opensearch.security.privileges.dlsfls.DocumentPrivileges;
         import org.opensearch.security.spi.resources.Resource;
         import org.opensearch.security.spi.resources.ResourceParser;
        +import org.opensearch.security.spi.resources.ResourceSharingException;
         import org.opensearch.security.support.ConfigConstants;
         import org.opensearch.security.user.User;
         import org.opensearch.threadpool.ThreadPool;
        @@ -82,54 +87,113 @@ public void initializeRecipientTypes() {
              * @param resourceIndex The resource index to check for accessible resources.
              * @return A set of accessible resource IDs.
              */
        -    public Set getAccessibleResourceIdsForCurrentUser(String resourceIndex) {
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
        +    public void getAccessibleResourceIdsForCurrentUser(String resourceIndex, ActionListener> listener) {
        +        final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent(
        +            ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER
        +        );
        +        final User user = (userSubject == null) ? null : userSubject.getUser();
        +
        +        // If no user is authenticated, return an empty set
                 if (user == null) {
        -            LOGGER.info("Unable to fetch user details ");
        -            return Collections.emptySet();
        +            LOGGER.info("Unable to fetch user details.");
        +            listener.onResponse(Collections.emptySet());
        +            return;
                 }
         
        -        LOGGER.info("Listing accessible resources within a resource index {} for : {}", resourceIndex, user.getName());
        -
        -        Set resourceIds = new HashSet<>();
        +        LOGGER.info("Listing accessible resources within the resource index {} for user: {}", resourceIndex, user.getName());
         
        -        // check if user is admin, if yes all resources should be accessible
        +        // 2. If the user is admin, simply fetch all resources
                 if (adminDNs.isAdmin(user)) {
        -            resourceIds.addAll(loadAllResources(resourceIndex));
        -            return resourceIds;
        +            loadAllResources(resourceIndex, new ActionListener<>() {
        +                @Override
        +                public void onResponse(Set allResources) {
        +                    listener.onResponse(allResources);
        +                }
        +
        +                @Override
        +                public void onFailure(Exception e) {
        +                    listener.onFailure(e);
        +                }
        +            });
        +            return;
                 }
         
        -        // 0. Own resources
        -        resourceIds.addAll(loadOwnResources(resourceIndex, user.getName()));
        +        // StepListener for the user’s "own" resources
        +        StepListener> ownResourcesListener = new StepListener<>();
        +
        +        // StepListener for resources shared with the user’s name
        +        StepListener> userNameResourcesListener = new StepListener<>();
        +
        +        // StepListener for resources shared with the user’s roles
        +        StepListener> rolesResourcesListener = new StepListener<>();
        +
        +        // StepListener for resources shared with the user’s backend roles
        +        StepListener> backendRolesResourcesListener = new StepListener<>();
         
        -        // 1. By username
        -        resourceIds.addAll(loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString()));
        +        // Load own resources for the user.
        +        loadOwnResources(resourceIndex, user.getName(), ownResourcesListener);
         
        -        // 2. By roles
        -        Set roles = user.getSecurityRoles();
        -        resourceIds.addAll(loadSharedWithResources(resourceIndex, roles, Recipient.ROLES.toString()));
        +        // Load resources shared with the user by its name.
        +        ownResourcesListener.whenComplete(ownResources -> {
        +            loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString(), userNameResourcesListener);
        +        }, listener::onFailure);
         
        -        // 3. By backend_roles
        -        Set backendRoles = user.getRoles();
        -        resourceIds.addAll(loadSharedWithResources(resourceIndex, backendRoles, Recipient.BACKEND_ROLES.toString()));
        +        // Load resources shared with the user’s roles.
        +        userNameResourcesListener.whenComplete(userNameResources -> {
        +            loadSharedWithResources(resourceIndex, user.getSecurityRoles(), Recipient.ROLES.toString(), rolesResourcesListener);
        +        }, listener::onFailure);
         
        -        return resourceIds;
        +        // Load resources shared with the user’s backend roles.
        +        rolesResourcesListener.whenComplete(rolesResources -> {
        +            loadSharedWithResources(resourceIndex, user.getRoles(), Recipient.BACKEND_ROLES.toString(), backendRolesResourcesListener);
        +        }, listener::onFailure);
        +
        +        // Combine all results and pass them back to the original listener.
        +        backendRolesResourcesListener.whenComplete(backendRolesResources -> {
        +            Set allResources = new HashSet<>();
        +
        +            // Retrieve results from each StepListener
        +            allResources.addAll(ownResourcesListener.result());
        +            allResources.addAll(userNameResourcesListener.result());
        +            allResources.addAll(rolesResourcesListener.result());
        +            allResources.addAll(backendRolesResourcesListener.result());
        +
        +            LOGGER.debug("Found {} accessible resources for user {}", allResources.size(), user.getName());
        +            listener.onResponse(allResources);
        +        }, listener::onFailure);
             }
         
             /**
              * Returns a set of accessible resources for the current user within the specified resource index.
              *
              * @param resourceIndex The resource index to check for accessible resources.
        -     * @return A set of accessible resource IDs.
              */
             @SuppressWarnings("unchecked")
        -    public  Set getAccessibleResourcesForCurrentUser(String resourceIndex) {
        -        validateArguments(resourceIndex);
        -        ResourceParser parser = OpenSearchSecurityPlugin.getResourceProviders().get(resourceIndex).getResourceParser();
        -        Set resourceIds = getAccessibleResourceIdsForCurrentUser(resourceIndex);
        -        return resourceIds.isEmpty()
        -            ? Set.of()
        -            : this.resourceSharingIndexHandler.getResourceDocumentsFromIds(resourceIds, resourceIndex, parser);
        +    public  void getAccessibleResourcesForCurrentUser(String resourceIndex, ActionListener> listener) {
        +        try {
        +            validateArguments(resourceIndex);
        +            ResourceParser parser = OpenSearchSecurityPlugin.getResourceProviders().get(resourceIndex).getResourceParser();
        +            Set resourceIds = getAccessibleResourceIdsForCurrentUser(resourceIndex);
        +
        +            if (resourceIds.isEmpty()) {
        +                listener.onResponse(Set.of());
        +                return;
        +            }
        +
        +            this.resourceSharingIndexHandler.getResourceDocumentsFromIds(
        +                resourceIds,
        +                resourceIndex,
        +                parser,
        +                ActionListener.wrap(
        +                    listener::onResponse,
        +                    exception -> listener.onFailure(
        +                        new ResourceSharingException("Failed to get accessible resources: " + exception.getMessage(), exception)
        +                    )
        +                )
        +            );
        +        } catch (Exception e) {
        +            listener.onFailure(new ResourceSharingException("Failed to process accessible resources request: " + e.getMessage(), e));
        +        }
             }
         
             /**
        @@ -138,40 +202,60 @@ public  Set getAccessibleResourcesForCurrentUser(String r
              * @param resourceId      The resource ID to check access for.
              * @param resourceIndex   The resource index containing the resource.
              * @param scope           The permission scope to check.
        -     * @return True if the user has the specified permission, false otherwise.
              */
        -    public boolean hasPermission(String resourceId, String resourceIndex, String scope) {
        +    public void hasPermission(String resourceId, String resourceIndex, String scope, ActionListener listener) {
                 validateArguments(resourceId, resourceIndex, scope);
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
        +        final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent(
        +            ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER
        +        );
        +        final User user = (userSubject == null) ? null : userSubject.getUser();
        +
        +        if (user == null) {
        +            LOGGER.warn("No authenticated user found in ThreadContext");
        +            listener.onResponse(false);
        +            return;
        +        }
         
        -        LOGGER.info("Checking if {} has {} permission to resource {}", user.getName(), scope, resourceId);
        +        LOGGER.info("Checking if user '{}' has '{}' permission to resource '{}'", user.getName(), scope, resourceId);
         
        -        // check if user is admin, if yes the user has permission
                 if (adminDNs.isAdmin(user)) {
        -            return true;
        +            LOGGER.info("User '{}' is admin, automatically granted '{}' permission on '{}'", user.getName(), scope, resourceId);
        +            listener.onResponse(true);
        +            return;
                 }
         
                 Set userRoles = user.getSecurityRoles();
                 Set userBackendRoles = user.getRoles();
         
        -        ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId);
        -        if (document == null) {
        -            LOGGER.warn("Resource {} not found in index {}", resourceId, resourceIndex);
        -            return false;  // If the document doesn't exist, no permissions can be granted
        -        }
        -
        -        if (isSharedWithEveryone(document)
        -            || isOwnerOfResource(document, user.getName())
        -            || isSharedWithEntity(document, Recipient.USERS, Set.of(user.getName()), scope)
        -            || isSharedWithEntity(document, Recipient.ROLES, userRoles, scope)
        -            || isSharedWithEntity(document, Recipient.BACKEND_ROLES, userBackendRoles, scope)) {
        -            LOGGER.info("User {} has {} access to {}", user.getName(), scope, resourceId);
        -            return true;
        -        }
        +        this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId, ActionListener.wrap(document -> {
        +            if (document == null) {
        +                LOGGER.warn("Resource '{}' not found in index '{}'", resourceId, resourceIndex);
        +                listener.onResponse(false);
        +                return;
        +            }
         
        -        LOGGER.info("User {} does not have {} access to {} ", user.getName(), scope, resourceId);
        -        return false;
        +            if (isSharedWithEveryone(document)
        +                || isOwnerOfResource(document, user.getName())
        +                || isSharedWithEntity(document, Recipient.USERS, Set.of(user.getName()), scope)
        +                || isSharedWithEntity(document, Recipient.ROLES, userRoles, scope)
        +                || isSharedWithEntity(document, Recipient.BACKEND_ROLES, userBackendRoles, scope)) {
        +
        +                LOGGER.info("User '{}' has '{}' permission to resource '{}'", user.getName(), scope, resourceId);
        +                listener.onResponse(true);
        +            } else {
        +                LOGGER.info("User '{}' does not have '{}' permission to resource '{}'", user.getName(), scope, resourceId);
        +                listener.onResponse(false);
        +            }
        +        }, exception -> {
        +            LOGGER.error(
        +                "Failed to fetch resource sharing document for resource '{}' in index '{}': {}",
        +                resourceId,
        +                resourceIndex,
        +                exception.getMessage()
        +            );
        +            listener.onFailure(exception);
        +        }));
             }
         
             /**
        @@ -179,18 +263,44 @@ public boolean hasPermission(String resourceId, String resourceIndex, String sco
              * @param resourceId The resource ID to share.
              * @param resourceIndex  The index where resource is store
              * @param shareWith The users, roles, and backend roles as well as scope to share the resource with.
        -     * @return The updated ResourceSharing document.
              */
        -    public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) {
        +    public void shareWith(String resourceId, String resourceIndex, ShareWith shareWith, ActionListener listener) {
                 validateArguments(resourceId, resourceIndex, shareWith);
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
        +        final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent(
        +            ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER
        +        );
        +        final User user = (userSubject == null) ? null : userSubject.getUser();
        +
        +        if (user == null) {
        +            LOGGER.warn("No authenticated user found in the ThreadContext.");
        +            listener.onFailure(new OpenSearchException("No authenticated user found."));
        +            return;
        +        }
        +
                 LOGGER.info("Sharing resource {} created by {} with {}", resourceId, user.getName(), shareWith.toString());
         
        -        // check if user is admin, if yes the user has permission
                 boolean isAdmin = adminDNs.isAdmin(user);
         
        -        return this.resourceSharingIndexHandler.updateResourceSharingInfo(resourceId, resourceIndex, user.getName(), shareWith, isAdmin);
        +        this.resourceSharingIndexHandler.updateResourceSharingInfo(
        +            resourceId,
        +            resourceIndex,
        +            user.getName(),
        +            shareWith,
        +            isAdmin,
        +            ActionListener.wrap(
        +                // On success, return the updated ResourceSharing
        +                updatedResourceSharing -> {
        +                    LOGGER.info("Successfully shared resource {} with {}", resourceId, shareWith.toString());
        +                    listener.onResponse(updatedResourceSharing);
        +                },
        +                // On failure, log and pass the exception along
        +                e -> {
        +                    LOGGER.error("Failed to share resource {} with {}: {}", resourceId, shareWith.toString(), e.getMessage());
        +                    listener.onFailure(e);
        +                }
        +            )
        +        );
             }
         
             /**
        @@ -201,44 +311,114 @@ public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareW
              * @param scopes The permission scopes to revoke access for.
              * @return The updated ResourceSharing document.
              */
        -    public ResourceSharing revokeAccess(
        +    public void revokeAccess(
                 String resourceId,
                 String resourceIndex,
                 Map> revokeAccess,
        -        Set scopes
        +        Set scopes,
        +        ActionListener listener
             ) {
        +        // Validate input
                 validateArguments(resourceId, resourceIndex, revokeAccess, scopes);
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
        -        LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes);
         
        -        // check if user is admin, if yes the user has permission
        -        boolean isAdmin = adminDNs.isAdmin(user);
        +        // Retrieve user
        +        final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent(
        +            ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER
        +        );
        +        final User user = (userSubject == null) ? null : userSubject.getUser();
        +
        +        if (user != null) {
        +            LOGGER.info("User {} revoking access to resource {} for {} for scopes {} ", user.getName(), resourceId, revokeAccess, scopes);
        +        } else {
        +            listener.onFailure(
        +                new ResourceSharingException(
        +                    "Failed to revoke access to resource {} for {} for scopes {} with no authenticated user",
        +                    resourceId,
        +                    revokeAccess,
        +                    scopes
        +                )
        +            );
        +        }
         
        -        return this.resourceSharingIndexHandler.revokeAccess(resourceId, resourceIndex, revokeAccess, scopes, user.getName(), isAdmin);
        +        boolean isAdmin = (user != null) && adminDNs.isAdmin(user);
        +
        +        this.resourceSharingIndexHandler.revokeAccess(
        +            resourceId,
        +            resourceIndex,
        +            revokeAccess,
        +            scopes,
        +            (user != null ? user.getName() : null),
        +            isAdmin,
        +            ActionListener.wrap(listener::onResponse, exception -> {
        +                LOGGER.error("Failed to revoke access to resource {} in index {}: {}", resourceId, resourceIndex, exception.getMessage());
        +                listener.onFailure(exception);
        +            })
        +        );
             }
         
             /**
              * Deletes a resource sharing record by its ID and the resource index it belongs to.
              * @param resourceId The resource ID to delete.
              * @param resourceIndex The resource index containing the resource.
        -     * @return True if the record was successfully deleted, false otherwise.
              */
        -    public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) {
        -        validateArguments(resourceId, resourceIndex);
        -
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
        -        LOGGER.info("Deleting resource sharing record for resource {} in {} created by {}", resourceId, resourceIndex, user.getName());
        +    public void deleteResourceSharingRecord(String resourceId, String resourceIndex, ActionListener listener) {
        +        try {
        +            validateArguments(resourceId, resourceIndex);
        +
        +            final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent(
        +                ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER
        +            );
        +            final User user = (userSubject == null) ? null : userSubject.getUser();
        +
        +            if (user != null) {
        +                LOGGER.info(
        +                    "Deleting resource sharing record for resource {} in {} created by {}",
        +                    resourceId,
        +                    resourceIndex,
        +                    user.getName()
        +                );
        +            } else {
        +                LOGGER.info("Deleting resource sharing record for resource {} in {} with no authenticated user", resourceId, resourceIndex);
        +            }
         
        -        ResourceSharing document = this.resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId);
        -        if (document == null) {
        -            LOGGER.info("Document {} does not exist in index {}", resourceId, resourceIndex);
        -            return false;
        +            resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId, ActionListener.wrap(document -> {
        +                if (document == null) {
        +                    LOGGER.info("Document {} does not exist in index {}", resourceId, resourceIndex);
        +                    listener.onResponse(false);
        +                    return;
        +                }
        +
        +                // Check if the user is allowed to delete
        +                boolean isAdmin = (user != null && adminDNs.isAdmin(user));
        +                boolean isOwner = (user != null && isOwnerOfResource(document, user.getName()));
        +
        +                if (!isAdmin && !isOwner) {
        +                    LOGGER.info(
        +                        "User {} does not have access to delete the record {}",
        +                        (user == null ? "UNKNOWN" : user.getName()),
        +                        resourceId
        +                    );
        +                    listener.onResponse(false);
        +                    return;
        +                }
        +
        +                // Finally, perform the actual record deletion (assuming it's a synchronous call)
        +                boolean result = resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex);
        +                listener.onResponse(result);
        +            }, exception -> {
        +                // If an error happens while fetching
        +                LOGGER.error(
        +                    "Failed to fetch resource sharing document for resource {} in index {}. Error: {}",
        +                    resourceId,
        +                    resourceIndex,
        +                    exception.getMessage()
        +                );
        +                listener.onFailure(exception);
        +            }));
        +        } catch (Exception e) {
        +            LOGGER.error("Failed to delete resource sharing record for resource {}", resourceId, e);
        +            listener.onFailure(e);
                 }
        -        if (!(adminDNs.isAdmin(user) || isOwnerOfResource(document, user.getName()))) {
        -            LOGGER.info("User {} does not have access to delete the record {} ", user.getName(), resourceId);
        -            return false;
        -        }
        -        return this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex);
             }
         
             /**
        @@ -247,7 +427,10 @@ public boolean deleteResourceSharingRecord(String resourceId, String resourceInd
              */
             public boolean deleteAllResourceSharingRecordsForCurrentUser() {
         
        -        final User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER);
        +        final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent(
        +            ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER
        +        );
        +        User user = userSubject == null ? null : userSubject.getUser();
                 LOGGER.info("Deleting all resource sharing records for resource {}", user.getName());
         
                 return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName());
        @@ -259,8 +442,8 @@ public boolean deleteAllResourceSharingRecordsForCurrentUser() {
              * @param resourceIndex The resource index to load resources from.
              * @return A set of resource IDs.
              */
        -    private Set loadAllResources(String resourceIndex) {
        -        return this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex);
        +    private void loadAllResources(String resourceIndex, ActionListener> listener) {
        +        this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, listener);
             }
         
             /**
        @@ -270,8 +453,8 @@ private Set loadAllResources(String resourceIndex) {
              * @param userName The username of the owner.
              * @return A set of resource IDs owned by the user.
              */
        -    private Set loadOwnResources(String resourceIndex, String userName) {
        -        return this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName);
        +    private void loadOwnResources(String resourceIndex, String userName, ActionListener> listener) {
        +        this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, listener);
             }
         
             /**
        @@ -279,14 +462,19 @@ private Set loadOwnResources(String resourceIndex, String userName) {
              *
              * @param resourceIndex The resource index to load resources from.
              * @param entities The set of entities to check for shared resources.
        -     * @param RecipientType The type of entity (e.g., users, roles, backend_roles).
        +     * @param recipientType The type of entity (e.g., users, roles, backend_roles).
              * @return A set of resource IDs shared with the specified entities.
              */
        -    private Set loadSharedWithResources(String resourceIndex, Set entities, String RecipientType) {
        +    private void loadSharedWithResources(
        +        String resourceIndex,
        +        Set entities,
        +        String recipientType,
        +        ActionListener> listener
        +    ) {
                 Set entitiesCopy = new HashSet<>(entities);
                 // To allow "public" resources to be matched for any user, role, backend_role
                 entitiesCopy.add("*");
        -        return this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entitiesCopy, RecipientType);
        +        this.resourceSharingIndexHandler.fetchDocumentsForAllScopes(resourceIndex, entitiesCopy, recipientType, listener);
             }
         
             /**
        diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        index da8376244f..576a146d3b 100644
        --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java
        @@ -26,23 +26,23 @@
         
         import org.opensearch.OpenSearchException;
         import org.opensearch.action.DocWriteRequest;
        +import org.opensearch.action.StepListener;
         import org.opensearch.action.admin.indices.create.CreateIndexRequest;
         import org.opensearch.action.admin.indices.create.CreateIndexResponse;
         import org.opensearch.action.get.MultiGetItemResponse;
         import org.opensearch.action.get.MultiGetRequest;
        -import org.opensearch.action.get.MultiGetResponse;
         import org.opensearch.action.index.IndexRequest;
         import org.opensearch.action.index.IndexResponse;
         import org.opensearch.action.search.ClearScrollRequest;
         import org.opensearch.action.search.SearchRequest;
         import org.opensearch.action.search.SearchResponse;
        -import org.opensearch.action.search.SearchScrollAction;
         import org.opensearch.action.search.SearchScrollRequest;
         import org.opensearch.action.support.WriteRequest;
         import org.opensearch.client.Client;
         import org.opensearch.common.unit.TimeValue;
         import org.opensearch.common.util.concurrent.ThreadContext;
         import org.opensearch.common.xcontent.LoggingDeprecationHandler;
        +import org.opensearch.common.xcontent.XContentFactory;
         import org.opensearch.common.xcontent.XContentHelper;
         import org.opensearch.common.xcontent.XContentType;
         import org.opensearch.core.action.ActionListener;
        @@ -216,14 +216,8 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn
             * 
             *
             * @param pluginIndex The source index to match against the source_idx field
        -    * @return Set containing resource IDs that belong to the specified system index.
        -    *         Returns an empty list if:
        -    *         
          - *
        • No matching documents are found
        • - *
        • An error occurs during the search operation
        • - *
        • The system index parameter is invalid
        • - *
        - * + * @param listener The listener to be notified when the operation completes. + * The listener receives a set of resource IDs as a result. * @apiNote This method: *
          *
        • Uses source filtering for optimal performance
        • @@ -231,40 +225,54 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn *
        • Returns an empty list instead of throwing exceptions
        • *
        */ - public Set fetchAllDocuments(String pluginIndex) { - LOGGER.debug("Fetching all documents from {} where source_idx = {}", resourceSharingIndex, pluginIndex); + public void fetchAllDocuments(String pluginIndex, ActionListener> listener) { + LOGGER.debug("Fetching all documents asynchronously from {} where source_idx = {}", resourceSharingIndex, pluginIndex); - // TODO: Once stashContext is replaced with switchContext this call will have to be modified - try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { + try (final ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext();) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); - - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); - searchSourceBuilder.query(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)); - searchSourceBuilder.size(10000); // TODO check what size should be set here. - - searchSourceBuilder.fetchSource(new String[] { "resource_id" }, null); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query( + QueryBuilders.termQuery("source_idx.keyword", pluginIndex) + ).size(10000).fetchSource(new String[] { "resource_id" }, null); searchRequest.source(searchSourceBuilder); - SearchResponse searchResponse = client.search(searchRequest).actionGet(); - - Set resourceIds = new HashSet<>(); + client.search(searchRequest, new ActionListener<>() { + @Override + public void onResponse(SearchResponse searchResponse) { + try { + Set resourceIds = new HashSet<>(); + + SearchHit[] hits = searchResponse.getHits().getHits(); + for (SearchHit hit : hits) { + Map sourceAsMap = hit.getSourceAsMap(); + if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { + resourceIds.add(sourceAsMap.get("resource_id").toString()); + } + } - SearchHit[] hits = searchResponse.getHits().getHits(); - for (SearchHit hit : hits) { - Map sourceAsMap = hit.getSourceAsMap(); - if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { - resourceIds.add(sourceAsMap.get("resource_id").toString()); + LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); + + listener.onResponse(resourceIds); + } catch (Exception e) { + LOGGER.error( + "Error while processing search response from {} for source_idx: {}", + resourceSharingIndex, + pluginIndex, + e + ); + listener.onFailure(e); + } } - } - - LOGGER.debug("Found {} documents in {} for source_idx: {}", resourceIds.size(), resourceSharingIndex, pluginIndex); - - return resourceIds; + @Override + public void onFailure(Exception e) { + LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); + listener.onFailure(e); + } + }); } catch (Exception e) { - LOGGER.error("Failed to fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); - return Set.of(); + LOGGER.error("Failed to initiate fetch documents from {} for source_idx: {}", resourceSharingIndex, pluginIndex, e); + listener.onFailure(e); } } @@ -313,15 +321,14 @@ public Set fetchAllDocuments(String pluginIndex) { * * @param pluginIndex The source index to match against the source_idx field * @param entities Set of values to match in the specified RecipientType field - * @param RecipientType The type of association with the resource. Must be one of: + * @param recipientType The type of association with the resource. Must be one of: *
          *
        • "users" - for user-based access
        • *
        • "roles" - for role-based access
        • *
        • "backend_roles" - for backend role-based access
        • *
        - * @return Set List of resource IDs that match the criteria. The list may be empty - * if no matches are found - * + * @param listener The listener to be notified when the operation completes. + * The listener receives a set of resource IDs as a result. * @throws RuntimeException if the search operation fails * * @apiNote This method: @@ -334,9 +341,14 @@ public Set fetchAllDocuments(String pluginIndex) { * */ - public Set fetchDocumentsForAllScopes(String pluginIndex, Set entities, String RecipientType) { + public void fetchDocumentsForAllScopes( + String pluginIndex, + Set entities, + String recipientType, + ActionListener> listener + ) { // "*" must match all scopes - return fetchDocumentsForAGivenScope(pluginIndex, entities, RecipientType, "*"); + fetchDocumentsForAGivenScope(pluginIndex, entities, recipientType, "*", listener); } /** @@ -384,16 +396,15 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set en * * @param pluginIndex The source index to match against the source_idx field * @param entities Set of values to match in the specified RecipientType field - * @param RecipientType The type of association with the resource. Must be one of: + * @param recipientType The type of association with the resource. Must be one of: *
          *
        • "users" - for user-based access
        • *
        • "roles" - for role-based access
        • *
        • "backend_roles" - for backend role-based access
        • *
        * @param scope The scope of the access. Should be implementation of {@link ResourceAccessScope} - * @return Set List of resource IDs that match the criteria. The list may be empty - * if no matches are found - * + * @param listener The listener to be notified when the operation completes. + * The listener receives a set of resource IDs as a result. * @throws RuntimeException if the search operation fails * * @apiNote This method: @@ -405,20 +416,25 @@ public Set fetchDocumentsForAllScopes(String pluginIndex, Set en *
      27. Properly cleans up scroll context after use
      28. * */ - public Set fetchDocumentsForAGivenScope(String pluginIndex, Set entities, String RecipientType, String scope) { + public void fetchDocumentsForAGivenScope( + String pluginIndex, + Set entities, + String recipientType, + String scope, + ActionListener> listener + ) { LOGGER.debug( - "Fetching documents from index: {}, where share_with.{}.{} contains any of {}", + "Fetching documents asynchronously from index: {}, where share_with.{}.{} contains any of {}", pluginIndex, scope, - RecipientType, + recipientType, entities ); - Set resourceIds = new HashSet<>(); + final Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); - // TODO: Once stashContext is replaced with switchContext this call will have to be modified - try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { + try (ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); searchRequest.scroll(scroll); @@ -428,36 +444,54 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set if ("*".equals(scope)) { for (String entity : entities) { shouldQuery.should( - QueryBuilders.multiMatchQuery(entity, "share_with.*." + RecipientType + ".keyword") + QueryBuilders.multiMatchQuery(entity, "share_with.*." + recipientType + ".keyword") .type(MultiMatchQueryBuilder.Type.BEST_FIELDS) ); } } else { for (String entity : entities) { - shouldQuery.should(QueryBuilders.termQuery("share_with." + scope + "." + RecipientType + ".keyword", entity)); + shouldQuery.should(QueryBuilders.termQuery("share_with." + scope + "." + recipientType + ".keyword", entity)); } } shouldQuery.minimumShouldMatch(1); boolQuery.must(QueryBuilders.existsQuery("share_with")).must(shouldQuery); - executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery); - - LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); - - return resourceIds; - + executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery, ActionListener.wrap(success -> { + try { + // If 'success' indicates the search completed, log and return the results + LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); + listener.onResponse(resourceIds); + } finally { + // Always close the stashed context + storedContext.close(); + } + }, exception -> { + try { + LOGGER.error( + "Search failed for pluginIndex={}, scope={}, recipientType={}, entities={}", + pluginIndex, + scope, + recipientType, + entities, + exception + ); + listener.onFailure(exception); + } finally { + storedContext.close(); + } + })); } catch (Exception e) { LOGGER.error( - "Failed to fetch documents from {} for criteria - pluginIndex: {}, scope: {}, RecipientType: {}, entities: {}", + "Failed to initiate fetch from {} for criteria - pluginIndex: {}, scope: {}, RecipientType: {}, entities: {}", resourceSharingIndex, pluginIndex, scope, - RecipientType, + recipientType, entities, e ); - throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); + listener.onFailure(new RuntimeException("Failed to fetch documents: " + e.getMessage(), e)); } } @@ -494,8 +528,8 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set * @param pluginIndex The source index to match against the source_idx field * @param field The field name to search in. Must be a valid field in the index mapping * @param value The value to match for the specified field. Performs exact term matching - * @return Set List of resource IDs that match the criteria. Returns an empty list - * if no matches are found + * @param listener The listener to be notified when the operation completes. + * The listener receives a set of resource IDs as a result. * * @throws IllegalArgumentException if any parameter is null or empty * @throws RuntimeException if the search operation fails, wrapping the underlying exception @@ -514,9 +548,10 @@ public Set fetchDocumentsForAGivenScope(String pluginIndex, Set * Set resources = fetchDocumentsByField("myIndex", "status", "active"); * */ - public Set fetchDocumentsByField(String pluginIndex, String field, String value) { + public void fetchDocumentsByField(String pluginIndex, String field, String value, ActionListener> listener) { if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(field) || StringUtils.isBlank(value)) { - throw new IllegalArgumentException("pluginIndex, field, and value must not be null or empty"); + listener.onFailure(new IllegalArgumentException("pluginIndex, field, and value must not be null or empty")); + return; } LOGGER.debug("Fetching documents from index: {}, where {} = {}", pluginIndex, field, value); @@ -533,15 +568,18 @@ public Set fetchDocumentsByField(String pluginIndex, String field, Strin .must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)) .must(QueryBuilders.termQuery(field + ".keyword", value)); - executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery); - - LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); - - return resourceIds; + executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery, ActionListener.wrap(success -> { + LOGGER.info("Found {} documents in {} where {} = {}", resourceIds.size(), resourceSharingIndex, field, value); + listener.onResponse(resourceIds); + }, exception -> { + LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, exception); + listener.onFailure(new RuntimeException("Failed to fetch documents: " + exception.getMessage(), exception)); + })); } catch (Exception e) { - LOGGER.error("Failed to fetch documents from {} where {} = {}", resourceSharingIndex, field, value, e); - throw new RuntimeException("Failed to fetch documents: " + e.getMessage(), e); + LOGGER.error("Failed to initiate fetch from {} where {} = {}", resourceSharingIndex, field, value, e); + listener.onFailure(new RuntimeException("Failed to initiate fetch: " + e.getMessage(), e)); } + } /** @@ -574,8 +612,8 @@ public Set fetchDocumentsByField(String pluginIndex, String field, Strin * * @param pluginIndex The source index to match against the source_idx field * @param resourceId The resource ID to fetch. Must exactly match the resource_id field - * @return ResourceSharing object if a matching document is found, null if no document - * matches the criteria + * @param listener The listener to be notified when the operation completes. + * The listener receives the parsed ResourceSharing object or null if not found * * @throws IllegalArgumentException if pluginIndexName or resourceId is null or empty * @throws RuntimeException if the search operation fails or parsing errors occur, @@ -598,53 +636,72 @@ public Set fetchDocumentsByField(String pluginIndex, String field, Strin * } * */ - - public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) { + public void fetchDocumentById(String pluginIndex, String resourceId, ActionListener listener) { if (StringUtils.isBlank(pluginIndex) || StringUtils.isBlank(resourceId)) { - throw new IllegalArgumentException("pluginIndexName and resourceId must not be null or empty"); + listener.onFailure(new IllegalArgumentException("pluginIndex and resourceId must not be null or empty")); + return; } + LOGGER.debug("Fetching document from index: {}, resourceId: {}", pluginIndex, resourceId); - LOGGER.debug("Fetching document from index: {}, with resourceId: {}", pluginIndex, resourceId); - - // TODO: Once stashContext is replaced with switchContext this call will have to be modified - try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { - SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); - + try (ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext()) { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)) .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)); - SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery).size(1); // We only need one document since - // a resource must have only one - // sharing entry - searchRequest.source(searchSourceBuilder); + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery).size(1); // There is only one document for + // a single resource - SearchResponse searchResponse = client.search(searchRequest).actionGet(); + SearchRequest searchRequest = new SearchRequest(resourceSharingIndex).source(searchSourceBuilder); - SearchHit[] hits = searchResponse.getHits().getHits(); - if (hits.length == 0) { - LOGGER.debug("No document found for resourceId: {} in index: {}", resourceId, pluginIndex); - return null; - } + client.search(searchRequest, new ActionListener<>() { + @Override + public void onResponse(SearchResponse searchResponse) { + try { + SearchHit[] hits = searchResponse.getHits().getHits(); + if (hits.length == 0) { + LOGGER.debug("No document found for resourceId: {} in index: {}", resourceId, pluginIndex); + listener.onResponse(null); + return; + } - SearchHit hit = hits[0]; - try ( - XContentParser parser = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, hit.getSourceAsString()) - ) { + SearchHit hit = hits[0]; + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, hit.getSourceAsString()) + ) { + parser.nextToken(); + ResourceSharing resourceSharing = ResourceSharing.fromXContent(parser); - parser.nextToken(); + LOGGER.debug("Successfully fetched document for resourceId: {} from index: {}", resourceId, pluginIndex); - ResourceSharing resourceSharing = ResourceSharing.fromXContent(parser); + listener.onResponse(resourceSharing); + } + } catch (Exception e) { + LOGGER.error("Failed to parse document for resourceId: {} from index: {}", resourceId, pluginIndex, e); + listener.onFailure( + new OpenSearchException( + "Failed to parse document for resourceId: " + resourceId + " from index: " + pluginIndex, + e + ) + ); + } + } - LOGGER.debug("Successfully fetched document for resourceId: {} from index: {}", resourceId, pluginIndex); + @Override + public void onFailure(Exception e) { - return resourceSharing; - } + LOGGER.error("Failed to fetch document for resourceId: {} from index: {}", resourceId, pluginIndex, e); + listener.onFailure( + new OpenSearchException("Failed to fetch document for resourceId: " + resourceId + " from index: " + pluginIndex, e) + ); + } + }); } catch (Exception e) { LOGGER.error("Failed to fetch document for resourceId: {} from index: {}", resourceId, pluginIndex, e); - throw new OpenSearchException("Failed to fetch document for resourceId: " + resourceId + " from index: " + pluginIndex, e); + listener.onFailure( + new OpenSearchException("Failed to fetch document for resourceId: " + resourceId + " from index: " + pluginIndex, e) + ); } } @@ -654,41 +711,100 @@ public ResourceSharing fetchDocumentById(String pluginIndex, String resourceId) * @param scroll Search Scroll * @param searchRequest Request to execute * @param boolQuery Query to execute with the request + * @param listener Listener to be notified when the operation completes */ - private void executeSearchRequest(Set resourceIds, Scroll scroll, SearchRequest searchRequest, BoolQueryBuilder boolQuery) { + private void executeSearchRequest( + Set resourceIds, + Scroll scroll, + SearchRequest searchRequest, + BoolQueryBuilder boolQuery, + ActionListener listener + ) { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(boolQuery) .size(1000) .fetchSource(new String[] { "resource_id" }, null); searchRequest.source(searchSourceBuilder); - SearchResponse searchResponse = client.search(searchRequest).actionGet(); - String scrollId = searchResponse.getScrollId(); - SearchHit[] hits = searchResponse.getHits().getHits(); + StepListener searchStep = new StepListener<>(); - while (hits != null && hits.length > 0) { - for (SearchHit hit : hits) { - Map sourceAsMap = hit.getSourceAsMap(); - if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { - resourceIds.add(sourceAsMap.get("resource_id").toString()); - } + client.search(searchRequest, searchStep); + + searchStep.whenComplete(initialResponse -> { + String scrollId = initialResponse.getScrollId(); + processScrollResults(resourceIds, scroll, scrollId, initialResponse.getHits().getHits(), listener); + }, listener::onFailure); + } + + /** + * Helper method to process scroll results recursively. + * @param resourceIds List to collect resource IDs + * @param scroll Search Scroll + * @param scrollId Scroll ID + * @param hits Search hits + * @param listener Listener to be notified when the operation completes + */ + private void processScrollResults( + Set resourceIds, + Scroll scroll, + String scrollId, + SearchHit[] hits, + ActionListener listener + ) { + // If no hits, clean up and complete + if (hits == null || hits.length == 0) { + clearScroll(scrollId, listener); + return; + } + + // Process current batch of hits + for (SearchHit hit : hits) { + Map sourceAsMap = hit.getSourceAsMap(); + if (sourceAsMap != null && sourceAsMap.containsKey("resource_id")) { + resourceIds.add(sourceAsMap.get("resource_id").toString()); } + } + + // Prepare next scroll request + SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); + scrollRequest.scroll(scroll); + + // Execute next scroll + client.searchScroll(scrollRequest, ActionListener.wrap(scrollResponse -> { + // Process next batch recursively + processScrollResults(resourceIds, scroll, scrollResponse.getScrollId(), scrollResponse.getHits().getHits(), listener); + }, e -> { + // Clean up scroll context on failure + clearScroll(scrollId, ActionListener.wrap(r -> listener.onFailure(e), ex -> { + e.addSuppressed(ex); + listener.onFailure(e); + })); + })); + } - SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); - scrollRequest.scroll(scroll); - searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); - scrollId = searchResponse.getScrollId(); - hits = searchResponse.getHits().getHits(); + /** + * Helper method to clear scroll context. + * @param scrollId Scroll ID + * @param listener Listener to be notified when the operation completes + */ + private void clearScroll(String scrollId, ActionListener listener) { + if (scrollId == null) { + listener.onResponse(null); + return; } ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); clearScrollRequest.addScrollId(scrollId); - client.clearScroll(clearScrollRequest).actionGet(); + + client.clearScroll(clearScrollRequest, ActionListener.wrap(r -> listener.onResponse(null), e -> { + LOGGER.warn("Failed to clear scroll context", e); + listener.onResponse(null); + })); } /** * Updates the sharing configuration for an existing resource in the resource sharing index. - * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set, String, boolean)} + * NOTE: This method only grants new access. To remove access use {@link #revokeAccess(String, String, Map, Set, String, boolean, ActionListener)} * This method modifies the sharing permissions for a specific resource identified by its * resource ID and source index. * @@ -704,93 +820,105 @@ private void executeSearchRequest(Set resourceIds, Scroll scroll, Search * } * } * @param isAdmin Boolean indicating whether the user requesting to share is an admin or not - * @return ResourceSharing Returns resourceSharing object if the update was successful, null otherwise + * @param listener Listener to be notified when the operation completes + * * @throws RuntimeException if there's an error during the update operation */ - public ResourceSharing updateResourceSharingInfo( + public void updateResourceSharingInfo( String resourceId, String sourceIdx, String requestUserName, ShareWith shareWith, - boolean isAdmin + boolean isAdmin, + ActionListener listener ) { XContentBuilder builder; Map shareWithMap; try { - builder = jsonBuilder(); + builder = XContentFactory.jsonBuilder(); shareWith.toXContent(builder, ToXContent.EMPTY_PARAMS); String json = builder.toString(); shareWithMap = DefaultObjectMapper.readValue(json, new TypeReference<>() { }); - } catch (IOException e) { LOGGER.error("Failed to build json content", e); - throw new OpenSearchException("Failed to build json content", e); + listener.onFailure(new OpenSearchException("Failed to build json content", e)); + return; } - // Check if the user requesting to share is the owner of the resource - // TODO Add a way for users who are not creators to be able to share the resource - ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); - if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { - LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); - throw new OpenSearchException("User " + requestUserName + " is not authorized to share resource " + resourceId); - } + StepListener fetchDocListener = new StepListener<>(); + StepListener updateScriptListener = new StepListener<>(); + StepListener updatedSharingListener = new StepListener<>(); - CreatedBy createdBy; - if (currentSharingInfo == null) { - createdBy = new CreatedBy(Creator.USER.getName(), requestUserName); - } else { - createdBy = currentSharingInfo.getCreatedBy(); - } + // Fetch resource sharing doc + fetchDocumentById(sourceIdx, resourceId, fetchDocListener); - // Atomic operation - Script updateScript = new Script(ScriptType.INLINE, "painless", """ - if (ctx._source.share_with == null) { - ctx._source.share_with = [:]; + // build update script + fetchDocListener.whenComplete(currentSharingInfo -> { + // Check if user can share. At present only the resource creator and admin is allowed to share the resource + if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { + + LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); + throw new OpenSearchException("User " + requestUserName + " is not authorized to share resource " + resourceId); } - for (def entry : params.shareWith.entrySet()) { - def scopeName = entry.getKey(); - def newScope = entry.getValue(); + Script updateScript = new Script(ScriptType.INLINE, "painless", """ + if (ctx._source.share_with == null) { + ctx._source.share_with = [:]; + } - if (!ctx._source.share_with.containsKey(scopeName)) { - def newScopeEntry = [:]; - for (def field : newScope.entrySet()) { - if (field.getValue() != null && !field.getValue().isEmpty()) { - newScopeEntry[field.getKey()] = new HashSet(field.getValue()); + for (def entry : params.shareWith.entrySet()) { + def scopeName = entry.getKey(); + def newScope = entry.getValue(); + + if (!ctx._source.share_with.containsKey(scopeName)) { + def newScopeEntry = [:]; + for (def field : newScope.entrySet()) { + if (field.getValue() != null && !field.getValue().isEmpty()) { + newScopeEntry[field.getKey()] = new HashSet(field.getValue()); + } } - } - ctx._source.share_with[scopeName] = newScopeEntry; - } else { - def existingScope = ctx._source.share_with[scopeName]; + ctx._source.share_with[scopeName] = newScopeEntry; + } else { + def existingScope = ctx._source.share_with[scopeName]; - for (def field : newScope.entrySet()) { - def fieldName = field.getKey(); - def newValues = field.getValue(); + for (def field : newScope.entrySet()) { + def fieldName = field.getKey(); + def newValues = field.getValue(); - if (newValues != null && !newValues.isEmpty()) { - if (!existingScope.containsKey(fieldName)) { - existingScope[fieldName] = new HashSet(); - } + if (newValues != null && !newValues.isEmpty()) { + if (!existingScope.containsKey(fieldName)) { + existingScope[fieldName] = new HashSet(); + } - for (def value : newValues) { - if (!existingScope[fieldName].contains(value)) { - existingScope[fieldName].add(value); + for (def value : newValues) { + if (!existingScope[fieldName].contains(value)) { + existingScope[fieldName].add(value); + } } } } } } - } - """, Collections.singletonMap("shareWith", shareWithMap)); + """, Collections.singletonMap("shareWith", shareWithMap)); - boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, updateScript); - if (!success) { - LOGGER.error("Failed to update resource sharing info for resource {}", resourceId); - throw new OpenSearchException("Failed to update resource sharing info for resource " + resourceId); - } + updateByQueryResourceSharing(sourceIdx, resourceId, updateScript, updateScriptListener); + + }, listener::onFailure); - return new ResourceSharing(resourceId, sourceIdx, createdBy, shareWith); + // Build & return the updated ResourceSharing + updateScriptListener.whenComplete(success -> { + if (!success) { + LOGGER.error("Failed to update resource sharing info for resource {}", resourceId); + listener.onResponse(null); + return; + } + // TODO check if this should be replaced by Java in-memory computation (current intuition is that it will be more memory + // intensive to do it in java) + fetchDocumentById(sourceIdx, resourceId, updatedSharingListener); + }, listener::onFailure); + + updatedSharingListener.whenComplete(listener::onResponse, listener::onFailure); } /** @@ -821,8 +949,7 @@ public ResourceSharing updateResourceSharingInfo( * @param resourceId The resource ID to match in the query (exact match) * @param updateScript The script containing the update operations to be performed. * This script defines how the matching documents should be modified - * @return boolean true if at least one document was updated, false if no documents - * were found or update failed + * @param listener Listener to be notified when the operation completes * * @apiNote This method: *
          @@ -846,8 +973,7 @@ public ResourceSharing updateResourceSharingInfo( * } * */ - private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId, Script updateScript) { - // TODO: Once stashContext is replaced with switchContext this call will have to be modified + private void updateByQueryResourceSharing(String sourceIdx, String resourceId, Script updateScript, ActionListener listener) { try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { BoolQueryBuilder query = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", sourceIdx)) @@ -857,24 +983,36 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId .setScript(updateScript) .setRefresh(true); - BulkByScrollResponse response = client.execute(UpdateByQueryAction.INSTANCE, ubq).actionGet(); + client.execute(UpdateByQueryAction.INSTANCE, ubq, new ActionListener<>() { + @Override + public void onResponse(BulkByScrollResponse response) { + long updated = response.getUpdated(); + if (updated > 0) { + LOGGER.info("Successfully updated {} documents in {}.", updated, resourceSharingIndex); + listener.onResponse(true); + } else { + LOGGER.info( + "No documents found to update in {} for source_idx: {} and resource_id: {}", + resourceSharingIndex, + sourceIdx, + resourceId + ); + listener.onResponse(false); + } - if (response.getUpdated() > 0) { - LOGGER.info("Successfully updated {} documents in {}.", response.getUpdated(), resourceSharingIndex); - return true; - } else { - LOGGER.info( - "No documents found to update in {} for source_idx: {} and resource_id: {}", - resourceSharingIndex, - sourceIdx, - resourceId - ); - return false; - } + } + + @Override + public void onFailure(Exception e) { + LOGGER.error("Failed to update documents in {}.", resourceSharingIndex, e); + listener.onFailure(e); + + } + }); } catch (Exception e) { - LOGGER.error("Failed to update documents in {}.", resourceSharingIndex, e); - return false; + LOGGER.error("Failed to update documents in {} before request submission.", resourceSharingIndex, e); + listener.onFailure(e); } } @@ -913,7 +1051,7 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * @param scopes A list of scopes to revoke access from. If null or empty, access is revoked from all scopes * @param requestUserName The user trying to revoke the accesses * @param isAdmin Boolean indicating whether the user is an admin or not - * @return The updated ResourceSharing object after revoking access, or null if the document doesn't exist + * @param listener Listener to be notified when the operation completes * @throws IllegalArgumentException if resourceId, sourceIdx is null/empty, or if revokeAccess is null/empty * @throws RuntimeException if the update operation fails or encounters an error * @@ -930,76 +1068,102 @@ private boolean updateByQueryResourceSharing(String sourceIdx, String resourceId * ResourceSharing updated = revokeAccess("resourceId", "pluginIndex", revokeAccess); * */ - public ResourceSharing revokeAccess( + public void revokeAccess( String resourceId, String sourceIdx, Map> revokeAccess, Set scopes, String requestUserName, - boolean isAdmin + boolean isAdmin, + ActionListener listener ) { if (StringUtils.isBlank(resourceId) || StringUtils.isBlank(sourceIdx) || revokeAccess == null || revokeAccess.isEmpty()) { - throw new IllegalArgumentException("resourceId, sourceIdx, and revokeAccess must not be null or empty"); + listener.onFailure(new IllegalArgumentException("resourceId, sourceIdx, and revokeAccess must not be null or empty")); + return; } - // TODO Check if access can be revoked by non-creator - ResourceSharing currentSharingInfo = fetchDocumentById(sourceIdx, resourceId); - if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { - LOGGER.error("User {} is not authorized to revoke access to resource {}", requestUserName, resourceId); - throw new OpenSearchException("User " + requestUserName + " is not authorized to revoke access to resource " + resourceId); - } + try (ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext()) { - LOGGER.debug("Revoking access for resource {} in {} for entities: {} and scopes: {}", resourceId, sourceIdx, revokeAccess, scopes); + LOGGER.debug( + "Revoking access for resource {} in {} for entities: {} and scopes: {}", + resourceId, + sourceIdx, + revokeAccess, + scopes + ); - // TODO: Once stashContext is replaced with switchContext this call will have to be modified - try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { - Map revoke = new HashMap<>(); - for (Map.Entry> entry : revokeAccess.entrySet()) { - revoke.put(entry.getKey().getType().toLowerCase(), new ArrayList<>(entry.getValue())); - } + StepListener currentSharingListener = new StepListener<>(); + StepListener revokeUpdateListener = new StepListener<>(); + StepListener updatedSharingListener = new StepListener<>(); - List scopesToUse = scopes != null ? new ArrayList<>(scopes) : new ArrayList<>(); + // Fetch the current ResourceSharing document + fetchDocumentById(sourceIdx, resourceId, currentSharingListener); - Script revokeScript = new Script(ScriptType.INLINE, "painless", """ - if (ctx._source.share_with != null) { - Set scopesToProcess = new HashSet(params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes); + // Check permissions & build revoke script + currentSharingListener.whenComplete(currentSharingInfo -> { + // Only admin or the creator of the resource is currently allowed to revoke access + if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { + throw new OpenSearchException( + "User " + requestUserName + " is not authorized to revoke access to resource " + resourceId + ); + } - for (def scopeName : scopesToProcess) { - if (ctx._source.share_with.containsKey(scopeName)) { - def existingScope = ctx._source.share_with.get(scopeName); + Map revoke = new HashMap<>(); + for (Map.Entry> entry : revokeAccess.entrySet()) { + revoke.put(entry.getKey().getType().toLowerCase(), new ArrayList<>(entry.getValue())); + } + List scopesToUse = (scopes != null) ? new ArrayList<>(scopes) : new ArrayList<>(); - for (def entry : params.revokeAccess.entrySet()) { - def RecipientType = entry.getKey(); - def entitiesToRemove = entry.getValue(); + // Build the revoke script + Script revokeScript = new Script(ScriptType.INLINE, "painless", """ + if (ctx._source.share_with != null) { + Set scopesToProcess = new HashSet(params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes); - if (existingScope.containsKey(RecipientType) && existingScope[RecipientType] != null) { - if (!(existingScope[RecipientType] instanceof HashSet)) { - existingScope[RecipientType] = new HashSet(existingScope[RecipientType]); - } + for (def scopeName : scopesToProcess) { + if (ctx._source.share_with.containsKey(scopeName)) { + def existingScope = ctx._source.share_with.get(scopeName); - existingScope[RecipientType].removeAll(entitiesToRemove); + for (def entry : params.revokeAccess.entrySet()) { + def RecipientType = entry.getKey(); + def entitiesToRemove = entry.getValue(); - if (existingScope[RecipientType].isEmpty()) { - existingScope.remove(RecipientType); + if (existingScope.containsKey(RecipientType) && existingScope[RecipientType] != null) { + if (!(existingScope[RecipientType] instanceof HashSet)) { + existingScope[RecipientType] = new HashSet(existingScope[RecipientType]); + } + + existingScope[RecipientType].removeAll(entitiesToRemove); + + if (existingScope[RecipientType].isEmpty()) { + existingScope.remove(RecipientType); + } } } - } - if (existingScope.isEmpty()) { - ctx._source.share_with.remove(scopeName); + if (existingScope.isEmpty()) { + ctx._source.share_with.remove(scopeName); + } } } } - } - """, Map.of("revokeAccess", revoke, "scopes", scopesToUse)); + """, Map.of("revokeAccess", revoke, "scopes", scopesToUse)); + updateByQueryResourceSharing(sourceIdx, resourceId, revokeScript, revokeUpdateListener); - boolean success = updateByQueryResourceSharing(sourceIdx, resourceId, revokeScript); + }, listener::onFailure); - return success ? fetchDocumentById(sourceIdx, resourceId) : null; + // Return doc or null based on successful result, fail otherwise + revokeUpdateListener.whenComplete(success -> { + if (!success) { + LOGGER.error("Failed to revoke access for resource {} in index {} (no docs updated).", resourceId, sourceIdx); + listener.onResponse(null); + return; + } + // TODO check if this should be replaced by Java in-memory computation (current intuition is that it will be more memory + // intensive to do it in java) + fetchDocumentById(sourceIdx, resourceId, updatedSharingListener); + }, listener::onFailure); - } catch (Exception e) { - LOGGER.error("Failed to revoke access for resource: {} in index: {}", resourceId, sourceIdx, e); - throw new RuntimeException("Failed to revoke access: " + e.getMessage(), e); + updatedSharingListener.whenComplete(listener::onResponse, listener::onFailure); } } @@ -1167,47 +1331,54 @@ public boolean deleteAllRecordsForUser(String name) { * @param parser The class to deserialize the documents into a specified type defined by the parser. * @return A set of deserialized documents. */ - public Set getResourceDocumentsFromIds( + public void getResourceDocumentsFromIds( Set resourceIds, String resourceIndex, - ResourceParser parser + ResourceParser parser, + ActionListener> listener ) { - Set result = new HashSet<>(); if (resourceIds.isEmpty()) { - return result; + listener.onResponse(new HashSet<>()); + return; } // stashing Context to avoid permission issues in-case resourceIndex is a system index - // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { MultiGetRequest request = new MultiGetRequest(); for (String id : resourceIds) { request.add(new MultiGetRequest.Item(resourceIndex, id)); } - MultiGetResponse response = client.multiGet(request).actionGet(); - - for (MultiGetItemResponse itemResponse : response.getResponses()) { - if (!itemResponse.isFailed() && itemResponse.getResponse().isExists()) { - BytesReference sourceAsString = itemResponse.getResponse().getSourceAsBytesRef(); - XContentParser xContentParser = XContentHelper.createParser( - NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, - sourceAsString, - XContentType.JSON - ); - T resource = parser.parseXContent(xContentParser); - result.add(resource); + client.multiGet(request, ActionListener.wrap(response -> { + Set result = new HashSet<>(); + try { + for (MultiGetItemResponse itemResponse : response.getResponses()) { + if (!itemResponse.isFailed() && itemResponse.getResponse().isExists()) { + BytesReference sourceAsString = itemResponse.getResponse().getSourceAsBytesRef(); + XContentParser xContentParser = XContentHelper.createParser( + NamedXContentRegistry.EMPTY, + LoggingDeprecationHandler.INSTANCE, + sourceAsString, + XContentType.JSON + ); + T resource = parser.parseXContent(xContentParser); + result.add(resource); + } + } + listener.onResponse(result); + } catch (Exception e) { + listener.onFailure(new OpenSearchException("Failed to parse resources: " + e.getMessage(), e)); } - } - } catch (IndexNotFoundException e) { - LOGGER.error("Index {} does not exist", resourceIndex, e); - throw e; - } catch (Exception e) { - LOGGER.error("Failed to fetch resources with ids {} from index {}", resourceIds, resourceIndex, e); - throw new OpenSearchException("Failed to fetch resources: " + e.getMessage(), e); + }, e -> { + if (e instanceof IndexNotFoundException) { + LOGGER.error("Index {} does not exist", resourceIndex, e); + listener.onFailure(e); + } else { + LOGGER.error("Failed to fetch resources with ids {} from index {}", resourceIds, resourceIndex, e); + listener.onFailure(new OpenSearchException("Failed to fetch resources: " + e.getMessage(), e)); + } + })); } - - return result; } + } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 649a21dfb1..140e0eca33 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -92,7 +92,7 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re ResourceSharing sharing = this.resourceSharingIndexHandler.indexResourceSharing( resourceId, resourceIndex, - new CreatedBy(Creator.USER.getName(), user.getName()), + new CreatedBy(Creator.USER, user.getName()), null ); log.info("Successfully created a resource sharing entry {}", sharing); diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java index 61935ee709..85fb04554b 100644 --- a/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java +++ b/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java @@ -10,12 +10,10 @@ import java.io.IOException; import java.util.List; -import java.util.Map; import com.google.common.collect.ImmutableList; import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; @@ -30,7 +28,7 @@ public RestListAccessibleResourcesAction() {} @Override public List routes() { - return addRoutesPrefix(ImmutableList.of(new Route(GET, "/resources/list")), PLUGIN_ROUTE_PREFIX); + return addRoutesPrefix(ImmutableList.of(new Route(GET, "/resources/list/{resourceIndex}")), PLUGIN_ROUTE_PREFIX); } @Override @@ -40,12 +38,7 @@ public String getName() { @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceIndex = (String) source.get("resource_index"); + String resourceIndex = request.param("resourceIndex", ""); final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(resourceIndex); return channel -> client.executeLocally( ListAccessibleResourcesAction.INSTANCE, diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java index e165b65436..25c727de67 100644 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java @@ -8,8 +8,6 @@ package org.opensearch.security.transport.resources.access; -import java.util.Set; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,7 +20,6 @@ import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesRequest; import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesResponse; -import org.opensearch.security.spi.resources.Resource; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -45,14 +42,22 @@ public TransportListAccessibleResourcesAction( @Override protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { try { - Set resources = resourceAccessHandler.getAccessibleResourcesForCurrentUser(request.getResourceIndex()); - log.info("Successfully fetched accessible resources for current user : {}", resources); - String resourceType = OpenSearchSecurityPlugin.getResourceProviders().get(request.getResourceIndex()).getResourceType(); - listener.onResponse(new ListAccessibleResourcesResponse(resourceType, resources)); + resourceAccessHandler.getAccessibleResourcesForCurrentUser(request.getResourceIndex(), ActionListener.wrap(resources -> { + try { + log.info("Successfully fetched accessible resources for current user : {}", resources); + String resourceType = OpenSearchSecurityPlugin.getResourceProviders().get(request.getResourceIndex()).getResourceType(); + listener.onResponse(new ListAccessibleResourcesResponse(resourceType, resources)); + } catch (Exception e) { + log.error("Failed to process accessible resources response", e); + listener.onFailure(e); + } + }, e -> { + log.error("Failed to list accessible resources for current user", e); + listener.onFailure(e); + })); } catch (Exception e) { - log.info("Failed to list accessible resources for current user: ", e); + log.error("Failed to initiate accessible resources request", e); listener.onFailure(e); } - } } diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java index 7a04e5d46f..97f139780d 100644 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java @@ -11,16 +11,15 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.OpenSearchException; import org.opensearch.action.support.ActionFilters; import org.opensearch.action.support.HandledTransportAction; import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.resources.ResourceSharing; import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessRequest; import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessResponse; +import org.opensearch.security.spi.resources.ResourceSharingException; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -41,25 +40,29 @@ public TransportRevokeResourceAccessAction( @Override protected void doExecute(Task task, RevokeResourceAccessRequest request, ActionListener listener) { try { - ResourceSharing revoke = revokeAccess(request); - if (revoke == null) { - log.error("Failed to revoke access to resource {}", request.getResourceId()); - listener.onFailure(new OpenSearchException("Failed to revoke access to resource " + request.getResourceId())); - return; - } - log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), revoke.toString()); - listener.onResponse(new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.")); + this.resourceAccessHandler.revokeAccess( + request.getResourceId(), + request.getResourceIndex(), + request.getRevokeAccess(), + request.getScopes(), + ActionListener.wrap(resourceSharing -> { + if (resourceSharing == null) { + log.error("Failed to revoke access to resource {}", request.getResourceId()); + listener.onFailure(new ResourceSharingException("Failed to revoke access to resource " + request.getResourceId())); + } else { + log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), resourceSharing.toString()); + listener.onResponse( + new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.") + ); + } + }, e -> { + log.error("Exception while revoking access to resource {}: {}", request.getResourceId(), e.getMessage(), e); + listener.onFailure(e); + }) + ); } catch (Exception e) { listener.onFailure(e); } } - private ResourceSharing revokeAccess(RevokeResourceAccessRequest request) { - return this.resourceAccessHandler.revokeAccess( - request.getResourceId(), - request.getResourceIndex(), - request.getRevokeAccess(), - request.getScopes() - ); - } } diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java index 4959de2ab2..0de7987dc4 100644 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java @@ -17,7 +17,6 @@ import org.opensearch.common.inject.Inject; import org.opensearch.core.action.ActionListener; import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.resources.ResourceSharing; import org.opensearch.security.rest.resources.access.share.ShareResourceAction; import org.opensearch.security.rest.resources.access.share.ShareResourceRequest; import org.opensearch.security.rest.resources.access.share.ShareResourceResponse; @@ -40,22 +39,27 @@ public TransportShareResourceAction( @Override protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { - ResourceSharing sharing = null; try { - sharing = shareResource(request); - if (sharing == null) { - log.error("Failed to share resource {}", request.getResourceId()); - listener.onFailure(new OpenSearchException("Failed to share resource " + request.getResourceId())); - return; - } - log.info("Shared resource : {} with {}", request.getResourceId(), sharing.toString()); - listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); + this.resourceAccessHandler.shareWith( + request.getResourceId(), + request.getResourceIndex(), + request.getShareWith(), + ActionListener.wrap(resourceSharing -> { + if (resourceSharing == null) { + log.error("Failed to share resource {}", request.getResourceId()); + listener.onFailure(new OpenSearchException("Failed to share resource " + request.getResourceId())); + } else { + log.info("Shared resource : {} with {}", request.getResourceId(), resourceSharing.toString()); + listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); + } + }, e -> { + log.error("Error while sharing resource {}: {}", request.getResourceId(), e.getMessage(), e); + listener.onFailure(e); + }) + ); } catch (Exception e) { + log.error("Exception while trying to share resource {}: {}", request.getResourceId(), e.getMessage(), e); listener.onFailure(e); } } - - private ResourceSharing shareResource(ShareResourceRequest request) throws Exception { - return this.resourceAccessHandler.shareWith(request.getResourceId(), request.getResourceIndex(), request.getShareWith()); - } } diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java index 0b732a1cb1..93965f9f0b 100644 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java +++ b/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java @@ -41,22 +41,33 @@ public TransportVerifyResourceAccessAction( @Override protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) { try { - boolean hasRequestedScopeAccess = this.resourceAccessHandler.hasPermission( + resourceAccessHandler.hasPermission( request.getResourceId(), request.getResourceIndex(), - request.getScope() - ); + request.getScope(), + new ActionListener<>() { + @Override + public void onResponse(Boolean hasRequestedScopeAccess) { + StringBuilder sb = new StringBuilder(); + sb.append("User "); + sb.append(hasRequestedScopeAccess ? "has" : "does not have"); + sb.append(" requested scope "); + sb.append(request.getScope()); + sb.append(" access to "); + sb.append(request.getResourceId()); + + log.info(sb.toString()); - StringBuilder sb = new StringBuilder(); - sb.append("User "); - sb.append(hasRequestedScopeAccess ? "has" : "does not have"); - sb.append(" requested scope "); - sb.append(request.getScope()); - sb.append(" access to "); - sb.append(request.getResourceId()); + listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); + } - log.info(sb.toString()); - listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); + @Override + public void onFailure(Exception e) { + log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); + listener.onFailure(e); + } + } + ); } catch (Exception e) { log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); listener.onFailure(e); diff --git a/src/test/java/org/opensearch/security/resources/CreatedByTests.java b/src/test/java/org/opensearch/security/resources/CreatedByTests.java index 346a949444..0bc651b4d5 100644 --- a/src/test/java/org/opensearch/security/resources/CreatedByTests.java +++ b/src/test/java/org/opensearch/security/resources/CreatedByTests.java @@ -32,7 +32,7 @@ public class CreatedByTests extends SingleClusterTest { - private static final String CREATOR_TYPE = "user"; + private static final Enum CREATOR_TYPE = Creator.USER; public void testCreatedByConstructorWithValidUser() { String expectedUser = "testUser"; @@ -45,7 +45,7 @@ public void testCreatedByFromStreamInput() throws IOException { String expectedUser = "testUser"; try (BytesStreamOutput out = new BytesStreamOutput()) { - out.writeString(CREATOR_TYPE); + out.writeEnum(Creator.valueOf(CREATOR_TYPE.name())); out.writeString(expectedUser); StreamInput in = out.bytes().streamInput(); From b5f29619cd25b845026b52069f2b5ce624f14695 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 16:06:59 -0500 Subject: [PATCH 094/122] Adds update sample request flow Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePlugin.java | 3 + .../rest/create/CreateResourceRestAction.java | 29 ++++++- .../rest/create/UpdateResourceAction.java | 29 +++++++ .../rest/create/UpdateResourceRequest.java | 58 ++++++++++++++ .../UpdateResourceTransportAction.java | 79 +++++++++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceAction.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceRequest.java create mode 100644 sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java index 11cbbcb308..6d386b85cb 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResourcePlugin.java @@ -39,10 +39,12 @@ import org.opensearch.rest.RestHandler; import org.opensearch.sample.resource.actions.rest.create.CreateResourceAction; import org.opensearch.sample.resource.actions.rest.create.CreateResourceRestAction; +import org.opensearch.sample.resource.actions.rest.create.UpdateResourceAction; import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceAction; import org.opensearch.sample.resource.actions.rest.delete.DeleteResourceRestAction; import org.opensearch.sample.resource.actions.transport.CreateResourceTransportAction; import org.opensearch.sample.resource.actions.transport.DeleteResourceTransportAction; +import org.opensearch.sample.resource.actions.transport.UpdateResourceTransportAction; import org.opensearch.script.ScriptService; import org.opensearch.security.spi.resources.ResourceParser; import org.opensearch.security.spi.resources.ResourceSharingExtension; @@ -94,6 +96,7 @@ public List getRestHandlers( public List> getActions() { return List.of( new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class), + new ActionHandler<>(UpdateResourceAction.INSTANCE, UpdateResourceTransportAction.class), new ActionHandler<>(DeleteResourceAction.INSTANCE, DeleteResourceTransportAction.class) ); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java index e990cc8a1d..02b6527a53 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java @@ -30,7 +30,7 @@ public CreateResourceRestAction() {} public List routes() { return List.of( new Route(PUT, "/_plugins/sample_resource_sharing/create"), - new Route(POST, "/_plugins/sample_resource_sharing/update") + new Route(POST, "/_plugins/sample_resource_sharing/update/{resourceId}") ); } @@ -46,6 +46,33 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli source = parser.map(); } + switch (request.method()) { + case PUT: + return createResource(source, client); + case POST: + return updateResource(source, request.param("resourceId"), client); + default: + throw new IllegalArgumentException("Illegal method: " + request.method()); + } + } + + private RestChannelConsumer updateResource(Map source, String resourceId, NodeClient client) throws IOException { + String name = (String) source.get("name"); + String description = source.containsKey("description") ? (String) source.get("description") : null; + Map attributes = source.containsKey("attributes") ? (Map) source.get("attributes") : null; + SampleResource resource = new SampleResource(); + resource.setName(name); + resource.setDescription(description); + resource.setAttributes(attributes); + final UpdateResourceRequest updateResourceRequest = new UpdateResourceRequest(resourceId, resource); + return channel -> client.executeLocally( + UpdateResourceAction.INSTANCE, + updateResourceRequest, + new RestToXContentListener<>(channel) + ); + } + + private RestChannelConsumer createResource(Map source, NodeClient client) throws IOException { String name = (String) source.get("name"); String description = source.containsKey("description") ? (String) source.get("description") : null; Map attributes = source.containsKey("attributes") ? (Map) source.get("attributes") : null; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceAction.java new file mode 100644 index 0000000000..129c2d1546 --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceAction.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.opensearch.sample.resource.actions.rest.create; + +import org.opensearch.action.ActionType; + +/** + * Action to update a sample resource + */ +public class UpdateResourceAction extends ActionType { + /** + * Create sample resource action instance + */ + public static final UpdateResourceAction INSTANCE = new UpdateResourceAction(); + /** + * Create sample resource action name + */ + public static final String NAME = "cluster:admin/sample-resource-plugin/update"; + + private UpdateResourceAction() { + super(NAME, CreateResourceResponse::new); + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceRequest.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceRequest.java new file mode 100644 index 0000000000..db74525a3a --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/UpdateResourceRequest.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package org.opensearch.sample.resource.actions.rest.create; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.security.spi.resources.Resource; + +/** + * Request object for UpdateResource transport action + */ +public class UpdateResourceRequest extends ActionRequest { + + private final String resourceId; + private final Resource resource; + + /** + * Default constructor + */ + public UpdateResourceRequest(String resourceId, Resource resource) { + this.resourceId = resourceId; + this.resource = resource; + } + + public UpdateResourceRequest(StreamInput in) throws IOException { + this.resourceId = in.readString(); + this.resource = in.readNamedWriteable(Resource.class); + } + + @Override + public void writeTo(final StreamOutput out) throws IOException { + out.writeString(resourceId); + resource.writeTo(out); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public Resource getResource() { + return this.resource; + } + + public String getResourceId() { + return this.resourceId; + } +} diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java new file mode 100644 index 0000000000..e9ec0127dd --- /dev/null +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package org.opensearch.sample.resource.actions.transport; + +import java.io.IOException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.action.update.UpdateRequest; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.sample.resource.actions.rest.create.*; +import org.opensearch.security.spi.resources.Resource; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; + +public class UpdateResourceTransportAction extends HandledTransportAction { + private static final Logger log = LogManager.getLogger(UpdateResourceTransportAction.class); + + private final TransportService transportService; + private final Client nodeClient; + + @Inject + public UpdateResourceTransportAction(TransportService transportService, ActionFilters actionFilters, Client nodeClient) { + super(UpdateResourceAction.NAME, transportService, actionFilters, UpdateResourceRequest::new); + this.transportService = transportService; + this.nodeClient = nodeClient; + } + + @Override + protected void doExecute(Task task, UpdateResourceRequest request, ActionListener listener) { + ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); + try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + updateResource(request, listener); + listener.onResponse( + new CreateResourceResponse("Resource " + request.getResource().getResourceName() + " updated successfully.") + ); + } catch (Exception e) { + log.info("Failed to update resource: {}", request.getResourceId(), e); + listener.onFailure(e); + } + } + + private void updateResource(UpdateResourceRequest request, ActionListener listener) { + String resourceId = request.getResourceId(); + Resource sample = request.getResource(); + try (XContentBuilder builder = jsonBuilder()) { + UpdateRequest ur = new UpdateRequest(RESOURCE_INDEX_NAME, resourceId).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .doc(sample.toXContent(builder, ToXContent.EMPTY_PARAMS)); + + log.info("Update Request: {}", ur.toString()); + + nodeClient.update( + ur, + ActionListener.wrap(updateResponse -> { log.info("Updated resource: {}", updateResponse.toString()); }, listener::onFailure) + ); + } catch (IOException e) { + listener.onFailure(new RuntimeException(e)); + } + + } +} From f8622230333cf6e12e8dc6bfaa9914e26a1bea15 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 16:32:26 -0500 Subject: [PATCH 095/122] Updates final methods to be async calss Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessHandler.java | 143 +++++++++------ .../ResourceSharingIndexHandler.java | 165 ++++++++++-------- .../ResourceSharingIndexListener.java | 14 +- 3 files changed, 187 insertions(+), 135 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index b1387e712c..98d244906a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -85,7 +85,8 @@ public void initializeRecipientTypes() { /** * Returns a set of accessible resource IDs for the current user within the specified resource index. * @param resourceIndex The resource index to check for accessible resources. - * @return A set of accessible resource IDs. + * @param listener The listener to be notified with the set of accessible resource IDs. + * */ public void getAccessibleResourceIdsForCurrentUser(String resourceIndex, ActionListener> listener) { final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent( @@ -134,19 +135,37 @@ public void onFailure(Exception e) { loadOwnResources(resourceIndex, user.getName(), ownResourcesListener); // Load resources shared with the user by its name. - ownResourcesListener.whenComplete(ownResources -> { - loadSharedWithResources(resourceIndex, Set.of(user.getName()), Recipient.USERS.toString(), userNameResourcesListener); - }, listener::onFailure); + ownResourcesListener.whenComplete( + ownResources -> loadSharedWithResources( + resourceIndex, + Set.of(user.getName()), + Recipient.USERS.toString(), + userNameResourcesListener + ), + listener::onFailure + ); // Load resources shared with the user’s roles. - userNameResourcesListener.whenComplete(userNameResources -> { - loadSharedWithResources(resourceIndex, user.getSecurityRoles(), Recipient.ROLES.toString(), rolesResourcesListener); - }, listener::onFailure); + userNameResourcesListener.whenComplete( + userNameResources -> loadSharedWithResources( + resourceIndex, + user.getSecurityRoles(), + Recipient.ROLES.toString(), + rolesResourcesListener + ), + listener::onFailure + ); // Load resources shared with the user’s backend roles. - rolesResourcesListener.whenComplete(rolesResources -> { - loadSharedWithResources(resourceIndex, user.getRoles(), Recipient.BACKEND_ROLES.toString(), backendRolesResourcesListener); - }, listener::onFailure); + rolesResourcesListener.whenComplete( + rolesResources -> loadSharedWithResources( + resourceIndex, + user.getRoles(), + Recipient.BACKEND_ROLES.toString(), + backendRolesResourcesListener + ), + listener::onFailure + ); // Combine all results and pass them back to the original listener. backendRolesResourcesListener.whenComplete(backendRolesResources -> { @@ -167,29 +186,36 @@ public void onFailure(Exception e) { * Returns a set of accessible resources for the current user within the specified resource index. * * @param resourceIndex The resource index to check for accessible resources. + * @param listener The listener to be notified with the set of accessible resources. */ @SuppressWarnings("unchecked") public void getAccessibleResourcesForCurrentUser(String resourceIndex, ActionListener> listener) { try { validateArguments(resourceIndex); + ResourceParser parser = OpenSearchSecurityPlugin.getResourceProviders().get(resourceIndex).getResourceParser(); - Set resourceIds = getAccessibleResourceIdsForCurrentUser(resourceIndex); - if (resourceIds.isEmpty()) { - listener.onResponse(Set.of()); - return; - } + StepListener> resourceIdsListener = new StepListener<>(); + StepListener> resourcesListener = new StepListener<>(); - this.resourceSharingIndexHandler.getResourceDocumentsFromIds( - resourceIds, - resourceIndex, - parser, - ActionListener.wrap( - listener::onResponse, - exception -> listener.onFailure( - new ResourceSharingException("Failed to get accessible resources: " + exception.getMessage(), exception) - ) - ) + // Fetch resource IDs + getAccessibleResourceIdsForCurrentUser(resourceIndex, resourceIdsListener); + + // Fetch docs + resourceIdsListener.whenComplete(resourceIds -> { + if (resourceIds.isEmpty()) { + // No accessible resources => immediately respond with empty set + listener.onResponse(Collections.emptySet()); + } else { + // Fetch the resource documents asynchronously + this.resourceSharingIndexHandler.getResourceDocumentsFromIds(resourceIds, resourceIndex, parser, resourcesListener); + } + }, listener::onFailure); + + // Send final response + resourcesListener.whenComplete( + listener::onResponse, + ex -> listener.onFailure(new ResourceSharingException("Failed to get accessible resources: " + ex.getMessage(), ex)) ); } catch (Exception e) { listener.onFailure(new ResourceSharingException("Failed to process accessible resources request: " + e.getMessage(), e)); @@ -202,6 +228,7 @@ public void getAccessibleResourcesForCurrentUser(String res * @param resourceId The resource ID to check access for. * @param resourceIndex The resource index containing the resource. * @param scope The permission scope to check. + * @param listener The listener to be notified with the permission check result. */ public void hasPermission(String resourceId, String resourceIndex, String scope, ActionListener listener) { validateArguments(resourceId, resourceIndex, scope); @@ -263,6 +290,7 @@ public void hasPermission(String resourceId, String resourceIndex, String scope, * @param resourceId The resource ID to share. * @param resourceIndex The index where resource is store * @param shareWith The users, roles, and backend roles as well as scope to share the resource with. + * @param listener The listener to be notified with the updated ResourceSharing document. */ public void shareWith(String resourceId, String resourceIndex, ShareWith shareWith, ActionListener listener) { validateArguments(resourceId, resourceIndex, shareWith); @@ -274,7 +302,7 @@ public void shareWith(String resourceId, String resourceIndex, ShareWith shareWi if (user == null) { LOGGER.warn("No authenticated user found in the ThreadContext."); - listener.onFailure(new OpenSearchException("No authenticated user found.")); + listener.onFailure(new ResourceSharingException("No authenticated user found.")); return; } @@ -309,7 +337,7 @@ public void shareWith(String resourceId, String resourceIndex, ShareWith shareWi * @param resourceIndex The index where resource is store * @param revokeAccess The users, roles, and backend roles to revoke access for. * @param scopes The permission scopes to revoke access for. - * @return The updated ResourceSharing document. + * @param listener The listener to be notified with the updated ResourceSharing document. */ public void revokeAccess( String resourceId, @@ -360,6 +388,7 @@ public void revokeAccess( * Deletes a resource sharing record by its ID and the resource index it belongs to. * @param resourceId The resource ID to delete. * @param resourceIndex The resource index containing the resource. + * @param listener The listener to be notified with the deletion result. */ public void deleteResourceSharingRecord(String resourceId, String resourceIndex, ActionListener listener) { try { @@ -378,17 +407,21 @@ public void deleteResourceSharingRecord(String resourceId, String resourceIndex, user.getName() ); } else { - LOGGER.info("Deleting resource sharing record for resource {} in {} with no authenticated user", resourceId, resourceIndex); + listener.onFailure(new ResourceSharingException("No authenticated user available.")); } - resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId, ActionListener.wrap(document -> { + StepListener fetchDocListener = new StepListener<>(); + StepListener deleteDocListener = new StepListener<>(); + + resourceSharingIndexHandler.fetchDocumentById(resourceIndex, resourceId, fetchDocListener); + + fetchDocListener.whenComplete(document -> { if (document == null) { LOGGER.info("Document {} does not exist in index {}", resourceId, resourceIndex); listener.onResponse(false); return; } - // Check if the user is allowed to delete boolean isAdmin = (user != null && adminDNs.isAdmin(user)); boolean isOwner = (user != null && isOwnerOfResource(document, user.getName())); @@ -398,23 +431,14 @@ public void deleteResourceSharingRecord(String resourceId, String resourceIndex, (user == null ? "UNKNOWN" : user.getName()), resourceId ); + // Not allowed => no deletion listener.onResponse(false); - return; + } else { + resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex, deleteDocListener); } + }, listener::onFailure); - // Finally, perform the actual record deletion (assuming it's a synchronous call) - boolean result = resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex); - listener.onResponse(result); - }, exception -> { - // If an error happens while fetching - LOGGER.error( - "Failed to fetch resource sharing document for resource {} in index {}. Error: {}", - resourceId, - resourceIndex, - exception.getMessage() - ); - listener.onFailure(exception); - })); + deleteDocListener.whenComplete(listener::onResponse, listener::onFailure); } catch (Exception e) { LOGGER.error("Failed to delete resource sharing record for resource {}", resourceId, e); listener.onFailure(e); @@ -423,24 +447,37 @@ public void deleteResourceSharingRecord(String resourceId, String resourceIndex, /** * Deletes all resource sharing records for the current user. - * @return True if all records were successfully deleted, false otherwise. + * @param listener The listener to be notified with the deletion result. */ - public boolean deleteAllResourceSharingRecordsForCurrentUser() { - + public void deleteAllResourceSharingRecordsForCurrentUser(ActionListener listener) { final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent( ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER ); - User user = userSubject == null ? null : userSubject.getUser(); - LOGGER.info("Deleting all resource sharing records for resource {}", user.getName()); + final User user = (userSubject == null) ? null : userSubject.getUser(); - return this.resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName()); + if (user == null) { + listener.onFailure(new OpenSearchException("No authenticated user available.")); + return; + } + + LOGGER.info("Deleting all resource sharing records for user {}", user.getName()); + + resourceSharingIndexHandler.deleteAllRecordsForUser(user.getName(), ActionListener.wrap(listener::onResponse, exception -> { + LOGGER.error( + "Failed to delete all resource sharing records for user {}: {}", + user.getName(), + exception.getMessage(), + exception + ); + listener.onFailure(exception); + })); } /** * Loads all resources within the specified resource index. * * @param resourceIndex The resource index to load resources from. - * @return A set of resource IDs. + * @param listener The listener to be notified with the set of resource IDs. */ private void loadAllResources(String resourceIndex, ActionListener> listener) { this.resourceSharingIndexHandler.fetchAllDocuments(resourceIndex, listener); @@ -451,7 +488,7 @@ private void loadAllResources(String resourceIndex, ActionListener> * * @param resourceIndex The resource index to load resources from. * @param userName The username of the owner. - * @return A set of resource IDs owned by the user. + * @param listener The listener to be notified with the set of resource IDs. */ private void loadOwnResources(String resourceIndex, String userName, ActionListener> listener) { this.resourceSharingIndexHandler.fetchDocumentsByField(resourceIndex, "created_by.user", userName, listener); @@ -463,7 +500,7 @@ private void loadOwnResources(String resourceIndex, String userName, ActionListe * @param resourceIndex The resource index to load resources from. * @param entities The set of entities to check for shared resources. * @param recipientType The type of entity (e.g., users, roles, backend_roles). - * @return A set of resource IDs shared with the specified entities. + * @param listener The listener to be notified with the set of resource IDs. */ private void loadSharedWithResources( String resourceIndex, diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 576a146d3b..1f88799b39 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -176,12 +176,13 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn .setOpType(DocWriteRequest.OpType.CREATE) // only create if an entry doesn't exist .request(); - ActionListener irListener = ActionListener.wrap(idxResponse -> { - LOGGER.info("Successfully created {} entry.", resourceSharingIndex); - }, (failResponse) -> { - LOGGER.error(failResponse.getMessage()); - LOGGER.info("Failed to create {} entry.", resourceSharingIndex); - }); + ActionListener irListener = ActionListener.wrap( + idxResponse -> LOGGER.info("Successfully created {} entry.", resourceSharingIndex), + (failResponse) -> { + LOGGER.error(failResponse.getMessage()); + LOGGER.info("Failed to create {} entry.", resourceSharingIndex); + } + ); client.index(ir, irListener); return entry; } catch (Exception e) { @@ -228,7 +229,7 @@ public ResourceSharing indexResourceSharing(String resourceId, String resourceIn public void fetchAllDocuments(String pluginIndex, ActionListener> listener) { LOGGER.debug("Fetching all documents asynchronously from {} where source_idx = {}", resourceSharingIndex, pluginIndex); - try (final ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext();) { + try (final ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query( QueryBuilders.termQuery("source_idx.keyword", pluginIndex) @@ -434,7 +435,7 @@ public void fetchDocumentsForAGivenScope( final Set resourceIds = new HashSet<>(); final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); - try (ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext()) { + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); searchRequest.scroll(scroll); @@ -458,28 +459,20 @@ public void fetchDocumentsForAGivenScope( boolQuery.must(QueryBuilders.existsQuery("share_with")).must(shouldQuery); executeSearchRequest(resourceIds, scroll, searchRequest, boolQuery, ActionListener.wrap(success -> { - try { - // If 'success' indicates the search completed, log and return the results - LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); - listener.onResponse(resourceIds); - } finally { - // Always close the stashed context - storedContext.close(); - } + LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); + listener.onResponse(resourceIds); + }, exception -> { - try { - LOGGER.error( - "Search failed for pluginIndex={}, scope={}, recipientType={}, entities={}", - pluginIndex, - scope, - recipientType, - entities, - exception - ); - listener.onFailure(exception); - } finally { - storedContext.close(); - } + LOGGER.error( + "Search failed for pluginIndex={}, scope={}, recipientType={}, entities={}", + pluginIndex, + scope, + recipientType, + entities, + exception + ); + listener.onFailure(exception); + })); } catch (Exception e) { LOGGER.error( @@ -643,7 +636,7 @@ public void fetchDocumentById(String pluginIndex, String resourceId, ActionListe } LOGGER.debug("Fetching document from index: {}, resourceId: {}", pluginIndex, resourceId); - try (ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext()) { + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("source_idx.keyword", pluginIndex)) .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)); @@ -1082,7 +1075,7 @@ public void revokeAccess( return; } - try (ThreadContext.StoredContext storedContext = this.threadPool.getThreadContext().stashContext()) { + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { LOGGER.debug( "Revoking access for resource {} in {} for entities: {} and scopes: {}", @@ -1114,7 +1107,6 @@ public void revokeAccess( } List scopesToUse = (scopes != null) ? new ArrayList<>(scopes) : new ArrayList<>(); - // Build the revoke script Script revokeScript = new Script(ScriptType.INLINE, "painless", """ if (ctx._source.share_with != null) { Set scopesToProcess = new HashSet(params.scopes.isEmpty() ? ctx._source.share_with.keySet() : params.scopes); @@ -1192,7 +1184,9 @@ public void revokeAccess( * * @param sourceIdx The source index to match in the query (exact match) * @param resourceId The resource ID to match in the query (exact match) - * @return boolean true if at least one document was deleted, false if no documents were found or deletion failed + * @param listener The listener to be notified when the operation completes + * @throws IllegalArgumentException if sourceIdx or resourceId is null/empty + * @throws RuntimeException if the delete operation fails or encounters an error * * @implNote The delete operation uses a bool query with two must clauses to ensure exact matching: *
          @@ -1208,10 +1202,14 @@ public void revokeAccess(
                * }
                * 
          */ - public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) { - LOGGER.debug("Deleting documents from {} where source_idx = {} and resource_id = {}", resourceSharingIndex, sourceIdx, resourceId); + public void deleteResourceSharingRecord(String resourceId, String sourceIdx, ActionListener listener) { + LOGGER.debug( + "Deleting documents asynchronously from {} where source_idx = {} and resource_id = {}", + resourceSharingIndex, + sourceIdx, + resourceId + ); - // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { DeleteByQueryRequest dbq = new DeleteByQueryRequest(resourceSharingIndex).setQuery( QueryBuilders.boolQuery() @@ -1219,24 +1217,36 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) .must(QueryBuilders.termQuery("resource_id.keyword", resourceId)) ).setRefresh(true); - BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, dbq).actionGet(); + client.execute(DeleteByQueryAction.INSTANCE, dbq, new ActionListener<>() { + @Override + public void onResponse(BulkByScrollResponse response) { - if (response.getDeleted() > 0) { - LOGGER.info("Successfully deleted {} documents from {}", response.getDeleted(), resourceSharingIndex); - return true; - } else { - LOGGER.info( - "No documents found to delete in {} for source_idx: {} and resource_id: {}", - resourceSharingIndex, - sourceIdx, - resourceId - ); - return false; - } + long deleted = response.getDeleted(); + if (deleted > 0) { + LOGGER.info("Successfully deleted {} documents from {}", deleted, resourceSharingIndex); + listener.onResponse(true); + } else { + LOGGER.info( + "No documents found to delete in {} for source_idx: {} and resource_id: {}", + resourceSharingIndex, + sourceIdx, + resourceId + ); + // No documents were deleted + listener.onResponse(false); + } + } + + @Override + public void onFailure(Exception e) { + LOGGER.error("Failed to delete documents from {}", resourceSharingIndex, e); + listener.onFailure(e); + } + }); } catch (Exception e) { - LOGGER.error("Failed to delete documents from {}", resourceSharingIndex, e); - return false; + LOGGER.error("Failed to delete documents from {} before request submission", resourceSharingIndex, e); + listener.onFailure(e); } } @@ -1265,13 +1275,7 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) * * * @param name The username to match against the created_by.user field - * @return boolean indicating whether the deletion was successful: - *
            - *
          • true - if one or more documents were deleted
          • - *
          • false - if no documents were found
          • - *
          • false - if the operation failed due to an error
          • - *
          - * + * @param listener The listener to be notified when the operation completes * @throws IllegalArgumentException if name is null or empty * * @@ -1293,34 +1297,42 @@ public boolean deleteResourceSharingRecord(String resourceId, String sourceIdx) * } * */ - public boolean deleteAllRecordsForUser(String name) { + public void deleteAllRecordsForUser(String name, ActionListener listener) { if (StringUtils.isBlank(name)) { - throw new IllegalArgumentException("Username must not be null or empty"); + listener.onFailure(new IllegalArgumentException("Username must not be null or empty")); + return; } - LOGGER.debug("Deleting all records for user {}", name); + LOGGER.debug("Deleting all records for user {} asynchronously", name); - // TODO: Once stashContext is replaced with switchContext this call will have to be modified try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(resourceSharingIndex).setQuery( QueryBuilders.termQuery("created_by.user", name) ).setRefresh(true); - BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, deleteRequest).actionGet(); - - long deletedDocs = response.getDeleted(); - - if (deletedDocs > 0) { - LOGGER.info("Successfully deleted {} documents created by user {}", deletedDocs, name); - return true; - } else { - LOGGER.info("No documents found for user {}", name); - return false; - } + client.execute(DeleteByQueryAction.INSTANCE, deleteRequest, new ActionListener<>() { + @Override + public void onResponse(BulkByScrollResponse response) { + long deletedDocs = response.getDeleted(); + if (deletedDocs > 0) { + LOGGER.info("Successfully deleted {} documents created by user {}", deletedDocs, name); + listener.onResponse(true); + } else { + LOGGER.info("No documents found for user {}", name); + // No documents matched => success = false + listener.onResponse(false); + } + } + @Override + public void onFailure(Exception e) { + LOGGER.error("Failed to delete documents for user {}", name, e); + listener.onFailure(e); + } + }); } catch (Exception e) { - LOGGER.error("Failed to delete documents for user {}", name, e); - return false; + LOGGER.error("Failed to delete documents for user {} before request submission", name, e); + listener.onFailure(e); } } @@ -1329,7 +1341,8 @@ public boolean deleteAllRecordsForUser(String name) { * * @param resourceIndex The resource index to fetch documents from. * @param parser The class to deserialize the documents into a specified type defined by the parser. - * @return A set of deserialized documents. + * @param listener The listener to be notified with the set of deserialized documents. + * @param The type of the deserialized documents. */ public void getResourceDocumentsFromIds( Set resourceIds, diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 140e0eca33..c3ca68356f 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -14,6 +14,7 @@ import org.apache.logging.log4j.Logger; import org.opensearch.client.Client; +import org.opensearch.core.action.ActionListener; import org.opensearch.core.index.shard.ShardId; import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexingOperationListener; @@ -116,11 +117,12 @@ public void postDelete(ShardId shardId, Engine.Delete delete, Engine.DeleteResul String resourceId = delete.id(); - boolean success = this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex); - if (success) { - log.info("Successfully deleted resource sharing entry for resource {}", resourceId); - } else { - log.info("Failed to delete resource sharing entry for resource {}", resourceId); - } + this.resourceSharingIndexHandler.deleteResourceSharingRecord(resourceId, resourceIndex, ActionListener.wrap(deleted -> { + if (deleted) { + log.info("Successfully deleted resource sharing entry for resource {}", resourceId); + } else { + log.info("No resource sharing entry found for resource {}", resourceId); + } + }, exception -> log.error("Failed to delete resource sharing entry for resource {}", resourceId, exception))); } } From fe94573dd0ded39777bdbb32f443a6a499b117d8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 16:38:51 -0500 Subject: [PATCH 096/122] Moves Resource sharing index constant to a new constants class Signed-off-by: Darshit Chanpura --- .../opensearch/security/OpenSearchSecurityPlugin.java | 9 +++------ .../security/resources/ResourceSharingConstants.java | 6 ++++++ .../security/resources/ResourceSharingIndexListener.java | 2 +- .../org/opensearch/security/support/ConfigConstants.java | 3 +-- 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 27340523d6..e6f88c00c1 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -178,10 +178,7 @@ import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext; import org.opensearch.security.resolver.IndexResolverReplacer; -import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.resources.ResourceSharingIndexHandler; -import org.opensearch.security.resources.ResourceSharingIndexListener; -import org.opensearch.security.resources.ResourceSharingIndexManagementRepository; +import org.opensearch.security.resources.*; import org.opensearch.security.rest.DashboardsInfoAction; import org.opensearch.security.rest.SecurityConfigUpdateAction; import org.opensearch.security.rest.SecurityHealthAction; @@ -1273,7 +1270,7 @@ public Collection createComponents( e.subscribeForChanges(dcf); } - final var resourceSharingIndex = ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; + final var resourceSharingIndex = ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler( resourceSharingIndex, localClient, @@ -2243,7 +2240,7 @@ public Collection getSystemIndexDescriptors(Settings sett ); final SystemIndexDescriptor securityIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); final SystemIndexDescriptor resourceSharingIndexDescriptor = new SystemIndexDescriptor( - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, + ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, "Resource Sharing index" ); return List.of(securityIndexDescriptor, resourceSharingIndexDescriptor); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java b/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java new file mode 100644 index 0000000000..1c171cfd18 --- /dev/null +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java @@ -0,0 +1,6 @@ +package org.opensearch.security.resources; + +public class ResourceSharingConstants { + // Resource sharing index + public static final String OPENSEARCH_RESOURCE_SHARING_INDEX = ".opensearch_resource_sharing"; +} diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index c3ca68356f..9b6f7f1832 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -60,7 +60,7 @@ public void initialize(ThreadPool threadPool, Client client, AuditLog auditLog) initialized = true; this.threadPool = threadPool; this.resourceSharingIndexHandler = new ResourceSharingIndexHandler( - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, + ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX, client, threadPool, auditLog diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 8069c291f5..9585995919 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -381,8 +381,7 @@ public enum RolesMappingResolution { // Variable for initial admin password support public static final String OPENSEARCH_INITIAL_ADMIN_PASSWORD = "OPENSEARCH_INITIAL_ADMIN_PASSWORD"; - // Resource sharing index - public static final String OPENSEARCH_RESOURCE_SHARING_INDEX = ".opensearch_resource_sharing"; + // Resource sharing feature-flag public static final String OPENSEARCH_RESOURCE_SHARING_ENABLED = SECURITY_SETTINGS_PREFIX + "resource_sharing.enabled"; public static final boolean OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT = true; From 2bf0ba177441c9272226fbef1b8a1c1ba8ce7a63 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 16:41:21 -0500 Subject: [PATCH 097/122] Uses ResourceSharingException Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessHandler.java | 3 +-- .../ResourceSharingIndexHandler.java | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 98d244906a..c2b67ab8d2 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -23,7 +23,6 @@ import org.apache.logging.log4j.Logger; import org.apache.lucene.search.Query; -import org.opensearch.OpenSearchException; import org.opensearch.action.StepListener; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.action.ActionListener; @@ -456,7 +455,7 @@ public void deleteAllResourceSharingRecordsForCurrentUser(ActionListener { // Only admin or the creator of the resource is currently allowed to revoke access if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { - throw new OpenSearchException( + throw new ResourceSharingException( "User " + requestUserName + " is not authorized to revoke access to resource " + resourceId ); } @@ -1380,7 +1383,7 @@ public void getResourceDocumentsFromIds( } listener.onResponse(result); } catch (Exception e) { - listener.onFailure(new OpenSearchException("Failed to parse resources: " + e.getMessage(), e)); + listener.onFailure(new ResourceSharingException("Failed to parse resources: " + e.getMessage(), e)); } }, e -> { if (e instanceof IndexNotFoundException) { @@ -1388,7 +1391,7 @@ public void getResourceDocumentsFromIds( listener.onFailure(e); } else { LOGGER.error("Failed to fetch resources with ids {} from index {}", resourceIds, resourceIndex, e); - listener.onFailure(new OpenSearchException("Failed to fetch resources: " + e.getMessage(), e)); + listener.onFailure(new ResourceSharingException("Failed to fetch resources: " + e.getMessage(), e)); } })); } From 47cb89dba6a84cfb7e07ae13a06cc242ebd3cc9f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 16:49:43 -0500 Subject: [PATCH 098/122] Adds correct license header Signed-off-by: Darshit Chanpura --- .../security/resources/ResourceSharingConstants.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java b/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java index 1c171cfd18..a6ed3f2b03 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingConstants.java @@ -1,3 +1,13 @@ +/* + * 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.security.resources; public class ResourceSharingConstants { From cd49948cae6e4d6e99064057678a3d0198e52503 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 17 Jan 2025 17:01:49 -0500 Subject: [PATCH 099/122] Fixes checkStyle violations :/ Signed-off-by: Darshit Chanpura --- .../org/opensearch/security/OpenSearchSecurityPlugin.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e6f88c00c1..fa0e93c323 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -178,7 +178,11 @@ import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; import org.opensearch.security.privileges.dlsfls.DlsFlsBaseContext; import org.opensearch.security.resolver.IndexResolverReplacer; -import org.opensearch.security.resources.*; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.resources.ResourceSharingConstants; +import org.opensearch.security.resources.ResourceSharingIndexHandler; +import org.opensearch.security.resources.ResourceSharingIndexListener; +import org.opensearch.security.resources.ResourceSharingIndexManagementRepository; import org.opensearch.security.rest.DashboardsInfoAction; import org.opensearch.security.rest.SecurityConfigUpdateAction; import org.opensearch.security.rest.SecurityHealthAction; @@ -2306,7 +2310,7 @@ public static Map getResourceProviders() { return ImmutableMap.copyOf(RESOURCE_PROVIDERS); } - // TODO following should be removed once core test framework allows loading extensions + // TODO following should be removed once core test framework allows loading extended classes @VisibleForTesting public static Map getResourceProvidersMutable() { return RESOURCE_PROVIDERS; From a215d077cd2958808b4404d915f77bfd1fcb9e8a Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 00:38:50 -0500 Subject: [PATCH 100/122] Updates route names to use constant prefix Signed-off-by: Darshit Chanpura --- .../actions/rest/create/CreateResourceRestAction.java | 5 +++-- .../actions/rest/delete/DeleteResourceRestAction.java | 3 ++- .../transport/CreateResourceTransportAction.java | 11 ++++------- .../java/org/opensearch/sample/utils/Constants.java | 3 +++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java index 02b6527a53..f1805e1820 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java @@ -21,6 +21,7 @@ import static org.opensearch.rest.RestRequest.Method.POST; import static org.opensearch.rest.RestRequest.Method.PUT; +import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_API_PREFIX; public class CreateResourceRestAction extends BaseRestHandler { @@ -29,8 +30,8 @@ public CreateResourceRestAction() {} @Override public List routes() { return List.of( - new Route(PUT, "/_plugins/sample_resource_sharing/create"), - new Route(POST, "/_plugins/sample_resource_sharing/update/{resourceId}") + new Route(PUT, SAMPLE_RESOURCE_PLUGIN_API_PREFIX + "/create"), + new Route(POST, SAMPLE_RESOURCE_PLUGIN_API_PREFIX + "/update/{resourceId}") ); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java index 6c88fdbc4d..699b5e0303 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/delete/DeleteResourceRestAction.java @@ -18,6 +18,7 @@ import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.DELETE; +import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_API_PREFIX; public class DeleteResourceRestAction extends BaseRestHandler { @@ -25,7 +26,7 @@ public DeleteResourceRestAction() {} @Override public List routes() { - return singletonList(new Route(DELETE, "/_plugins/sample_resource_sharing/delete/{resource_id}")); + return singletonList(new Route(DELETE, SAMPLE_RESOURCE_PLUGIN_API_PREFIX + "/delete/{resource_id}")); } @Override diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java index c20f492985..21c994f7fa 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java @@ -51,9 +51,6 @@ protected void doExecute(Task task, CreateResourceRequest request, ActionListene ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { createResource(request, listener); - listener.onResponse( - new CreateResourceResponse("Resource " + request.getResource().getResourceName() + " created successfully.") - ); } catch (Exception e) { log.info("Failed to create resource", e); listener.onFailure(e); @@ -71,10 +68,10 @@ private void createResource(CreateResourceRequest request, ActionListener { log.info("Created resource: {}", idxResponse.toString()); }, listener::onFailure) - ); + nodeClient.index(ir, ActionListener.wrap(idxResponse -> { + log.info("Created resource: {}", idxResponse.getId()); + listener.onResponse(new CreateResourceResponse("Created resource: " + idxResponse.getId())); + }, listener::onFailure)); } catch (IOException e) { listener.onFailure(new RuntimeException(e)); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java index ff7404d2cd..3be49d033e 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/utils/Constants.java @@ -10,4 +10,7 @@ public class Constants { public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin"; + + public static final String SAMPLE_RESOURCE_PLUGIN_PREFIX = "_plugins/sample_resource_sharing"; + public static final String SAMPLE_RESOURCE_PLUGIN_API_PREFIX = "/" + SAMPLE_RESOURCE_PLUGIN_PREFIX; } From 86d89d52b1f755ad82a25b76c02afbc064ab32a9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 00:40:54 -0500 Subject: [PATCH 101/122] Updates dls rules and updates a failing test suite Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 59 +++++++++++-------- .../configuration/DlsFlsValveImpl.java | 42 +++++++++---- .../SecurityFlsDlsIndexSearcherWrapper.java | 3 +- .../resources/ResourceAccessHandler.java | 10 ++-- .../security/IndexIntegrationTests.java | 10 ++-- 5 files changed, 76 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index fa0e93c323..87a2fb4989 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -49,6 +49,8 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; @@ -58,7 +60,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; @@ -828,7 +829,10 @@ public Weight doCache(Weight weight, QueryCachingPolicy policy) { @Override public void onPreQueryPhase(SearchContext context) { - dlsFlsValve.handleSearchContext(context, threadPool, namedXContentRegistry.get()); + CompletableFuture.runAsync( + () -> { dlsFlsValve.handleSearchContext(context, threadPool, namedXContentRegistry.get()); }, + threadPool.generic() + ).orTimeout(5, TimeUnit.SECONDS).join(); } @Override @@ -1194,6 +1198,22 @@ public Collection createComponents( namedXContentRegistry.get() ); + final var resourceSharingIndex = ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; + ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler( + resourceSharingIndex, + localClient, + threadPool, + auditLog + ); + resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); + resourceAccessHandler.initializeRecipientTypes(); + // Resource Sharing index is enabled by default + boolean isResourceSharingEnabled = settings.getAsBoolean( + ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, + ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT + ); + rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler, isResourceSharingEnabled); + dlsFlsBaseContext = new DlsFlsBaseContext(evaluator, threadPool.getThreadContext(), adminDns); if (SSLConfig.isSslOnlyMode()) { @@ -1274,22 +1294,6 @@ public Collection createComponents( e.subscribeForChanges(dcf); } - final var resourceSharingIndex = ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; - ResourceSharingIndexHandler rsIndexHandler = new ResourceSharingIndexHandler( - resourceSharingIndex, - localClient, - threadPool, - auditLog - ); - resourceAccessHandler = new ResourceAccessHandler(threadPool, rsIndexHandler, adminDns); - resourceAccessHandler.initializeRecipientTypes(); - // Resource Sharing index is enabled by default - boolean isResourceSharingEnabled = settings.getAsBoolean( - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT - ); - rmr = ResourceSharingIndexManagementRepository.create(rsIndexHandler, isResourceSharingEnabled); - components.add(adminDns); components.add(cr); components.add(xffResolver); @@ -2190,10 +2194,12 @@ public void onNodeStarted(DiscoveryNode localNode) { } // rmr will be null when sec plugin is disabled or is in SSLOnly mode, hence rmr will not be instantiated - if (settings.getAsBoolean( - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT - ) && rmr != null) { + if (settings != null + && settings.getAsBoolean( + ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, + ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT + ) + && rmr != null) { // create resource sharing index if absent rmr.createResourceSharingIndexIfAbsent(); } @@ -2310,14 +2316,17 @@ public static Map getResourceProviders() { return ImmutableMap.copyOf(RESOURCE_PROVIDERS); } + public static Set getResourceIndices() { + return ImmutableSet.copyOf(RESOURCE_INDICES); + } + // TODO following should be removed once core test framework allows loading extended classes - @VisibleForTesting public static Map getResourceProvidersMutable() { return RESOURCE_PROVIDERS; } - public static Set getResourceIndices() { - return ImmutableSet.copyOf(RESOURCE_INDICES); + public static Set getResourceIndicesMutable() { + return RESOURCE_INDICES; } // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index b776284af5..18e02fab30 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -17,6 +17,7 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.stream.StreamSupport; @@ -385,27 +386,42 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo PrivilegesEvaluationContext privilegesEvaluationContext = this.dlsFlsBaseContext.getPrivilegesEvaluationContext(); - if (privilegesEvaluationContext == null && !OpenSearchSecurityPlugin.getResourceIndices().contains(index)) { + if (privilegesEvaluationContext == null) { return; } DlsFlsProcessedConfig config = this.dlsFlsProcessedConfig.get(); if (this.isResourceSharingEnabled && OpenSearchSecurityPlugin.getResourceIndices().contains(index)) { - this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(index, ActionListener.wrap(resourceIds -> { - - log.info("Creating a DLS restriction for resource IDs: {}", resourceIds); - // Create a DLS restriction to filter search results with accessible resources only - DlsRestriction dlsRestriction = this.resourceAccessHandler.createResourceDLSRestriction( - resourceIds, - namedXContentRegistry + CountDownLatch latch = new CountDownLatch(1); + + threadPool.generic() + .submit( + () -> this.resourceAccessHandler.getAccessibleResourceIdsForCurrentUser(index, ActionListener.wrap(resourceIds -> { + try { + log.info("Creating a DLS restriction for resource IDs: {}", resourceIds); + // Create a DLS restriction and apply it + DlsRestriction dlsRestriction = this.resourceAccessHandler.createResourceDLSRestriction( + resourceIds, + namedXContentRegistry + ); + applyDlsRestrictionToSearchContext(dlsRestriction, index, searchContext, mode); + } catch (Exception e) { + log.error("Error while creating or applying DLS restriction for index '{}': {}", index, e.getMessage()); + applyDlsRestrictionToSearchContext(DlsRestriction.FULL, index, searchContext, mode); + } finally { + latch.countDown(); // Release the latch + } + }, exception -> { + log.error("Failed to fetch resource IDs for index '{}': {}", index, exception.getMessage()); + // Apply a default restriction on failure + applyDlsRestrictionToSearchContext(DlsRestriction.FULL, index, searchContext, mode); + latch.countDown(); + })) ); - applyDlsRestrictionToSearchContext(dlsRestriction, index, searchContext, mode); - }, exception -> { - log.error("Failed to fetch resource IDs for index '{}': {}", index, exception.getMessage()); - applyDlsRestrictionToSearchContext(DlsRestriction.FULL, index, searchContext, mode); - })); + } else { + // Synchronous path for non-resource-sharing-enabled cases DlsRestriction dlsRestriction = config.getDocumentPrivileges().getRestriction(privilegesEvaluationContext, index); applyDlsRestrictionToSearchContext(dlsRestriction, index, searchContext, mode); } diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java index af0a1a9282..9a05f4f50b 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java @@ -132,10 +132,11 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm } // 1. If user is admin, or we have no shard/index info, just wrap with default logic (no doc-level restriction). - if (isAdmin || Strings.isNullOrEmpty(indexName)) { + if (isAdmin || privilegesEvaluationContext == null) { return wrapWithDefaultDlsFls(reader, shardId); } + assert !Strings.isNullOrEmpty(indexName); // 2. If resource sharing is disabled or this is not a resource index, fallback to standard DLS/FLS logic. if (!this.isResourceSharingEnabled || !OpenSearchSecurityPlugin.getResourceIndices().contains(indexName)) { return wrapStandardDlsFls(privilegesEvaluationContext, reader, shardId, indexName, isAdmin); diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index c2b67ab8d2..49653ca3b5 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Future; import com.fasterxml.jackson.core.JsonProcessingException; import org.apache.logging.log4j.LogManager; @@ -87,7 +88,7 @@ public void initializeRecipientTypes() { * @param listener The listener to be notified with the set of accessible resource IDs. * */ - public void getAccessibleResourceIdsForCurrentUser(String resourceIndex, ActionListener> listener) { + public Future getAccessibleResourceIdsForCurrentUser(String resourceIndex, ActionListener> listener) { final UserSubjectImpl userSubject = (UserSubjectImpl) threadContext.getPersistent( ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER ); @@ -97,7 +98,7 @@ public void getAccessibleResourceIdsForCurrentUser(String resourceIndex, ActionL if (user == null) { LOGGER.info("Unable to fetch user details."); listener.onResponse(Collections.emptySet()); - return; + return null; } LOGGER.info("Listing accessible resources within the resource index {} for user: {}", resourceIndex, user.getName()); @@ -115,7 +116,7 @@ public void onFailure(Exception e) { listener.onFailure(e); } }); - return; + return null; } // StepListener for the user’s "own" resources @@ -179,6 +180,7 @@ public void onFailure(Exception e) { LOGGER.debug("Found {} accessible resources for user {}", allResources.size(), user.getName()); listener.onResponse(allResources); }, listener::onFailure); + return null; } /** @@ -627,7 +629,7 @@ public DlsRestriction createResourceDLSRestriction(Set resourceIds, Name // resourceIds.isEmpty() is true when user doesn't have access to any resources if (resourceIds.isEmpty()) { - LOGGER.debug("No resources found for user"); + LOGGER.info("No resources found for user. Enforcing full restriction."); return DlsRestriction.FULL; } diff --git a/src/test/java/org/opensearch/security/IndexIntegrationTests.java b/src/test/java/org/opensearch/security/IndexIntegrationTests.java index 648a9b1ade..14b84614c4 100644 --- a/src/test/java/org/opensearch/security/IndexIntegrationTests.java +++ b/src/test/java/org/opensearch/security/IndexIntegrationTests.java @@ -846,19 +846,19 @@ public void testIndexResolveMinus() throws Exception { resc = rh.executeGetRequest("/*,-foo*/_search", encodeBasicHeader("foo_all", "nagilum")); assertThat(resc.getStatusCode(), is(HttpStatus.SC_FORBIDDEN)); - resc = rh.executeGetRequest("/*,-*security/_search", encodeBasicHeader("foo_all", "nagilum")); + resc = rh.executeGetRequest("/*,-*security,-*resource*/_search", encodeBasicHeader("foo_all", "nagilum")); assertThat(resc.getStatusCode(), is(HttpStatus.SC_OK)); - resc = rh.executeGetRequest("/*,-*security/_search", encodeBasicHeader("foo_all", "nagilum")); + resc = rh.executeGetRequest("/*,-*security,-*resource*/_search", encodeBasicHeader("foo_all", "nagilum")); assertThat(resc.getStatusCode(), is(HttpStatus.SC_OK)); - resc = rh.executeGetRequest("/*,-*security,-foo*/_search", encodeBasicHeader("foo_all", "nagilum")); + resc = rh.executeGetRequest("/*,-*security,-foo*,-*resource*/_search", encodeBasicHeader("foo_all", "nagilum")); assertThat(resc.getStatusCode(), is(HttpStatus.SC_OK)); - resc = rh.executeGetRequest("/_all,-*security/_search", encodeBasicHeader("foo_all", "nagilum")); + resc = rh.executeGetRequest("/_all,-*security,-*resource*/_search", encodeBasicHeader("foo_all", "nagilum")); assertThat(resc.getStatusCode(), is(HttpStatus.SC_FORBIDDEN)); - resc = rh.executeGetRequest("/_all,-*security/_search", encodeBasicHeader("nagilum", "nagilum")); + resc = rh.executeGetRequest("/_all,-*security,-*resource*/_search", encodeBasicHeader("nagilum", "nagilum")); assertThat(resc.getStatusCode(), is(HttpStatus.SC_BAD_REQUEST)); } From b20cf18023a11116216286498dbd36cfcb0c4eb2 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 03:59:37 -0500 Subject: [PATCH 102/122] Corrects enum calls Signed-off-by: Darshit Chanpura --- .../org/opensearch/security/resources/CreatedBy.java | 2 +- .../org/opensearch/security/resources/Creator.java | 9 +++++++++ .../opensearch/security/resources/RecipientType.java | 12 ++---------- .../security/resources/ResourceAccessHandler.java | 6 +++--- .../resources/ResourceSharingIndexHandler.java | 12 ++++++++---- .../security/resources/SharedWithScope.java | 4 ++-- .../access/revoke/RevokeResourceAccessRequest.java | 2 +- .../resources/RecipientTypeRegistryTests.java | 2 +- 8 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/CreatedBy.java b/src/main/java/org/opensearch/security/resources/CreatedBy.java index 69af99719e..af27001663 100644 --- a/src/main/java/org/opensearch/security/resources/CreatedBy.java +++ b/src/main/java/org/opensearch/security/resources/CreatedBy.java @@ -74,7 +74,7 @@ public static CreatedBy fromXContent(XContentParser parser) throws IOException { while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { - creatorType = Creator.valueOf(parser.currentName()); + creatorType = Creator.fromName(parser.currentName()); } else if (token == XContentParser.Token.VALUE_STRING) { creator = parser.text(); } diff --git a/src/main/java/org/opensearch/security/resources/Creator.java b/src/main/java/org/opensearch/security/resources/Creator.java index c7a913d4de..ee2e9de7ab 100644 --- a/src/main/java/org/opensearch/security/resources/Creator.java +++ b/src/main/java/org/opensearch/security/resources/Creator.java @@ -20,4 +20,13 @@ public enum Creator { public String getName() { return name; } + + public static Creator fromName(String name) { + for (Creator creator : values()) { + if (creator.name.equalsIgnoreCase(name)) { // Case-insensitive comparison + return creator; + } + } + throw new IllegalArgumentException("No enum constant for name: " + name); + } } diff --git a/src/main/java/org/opensearch/security/resources/RecipientType.java b/src/main/java/org/opensearch/security/resources/RecipientType.java index 6ed3004b7e..bfe2bfec12 100644 --- a/src/main/java/org/opensearch/security/resources/RecipientType.java +++ b/src/main/java/org/opensearch/security/resources/RecipientType.java @@ -12,18 +12,10 @@ * This class determines a type of recipient a resource can be shared with. * An example type would be a user or a role. * This class is used to determine the type of recipient a resource can be shared with. + * * @opensearch.experimental */ -public class RecipientType { - private final String type; - - public RecipientType(String type) { - this.type = type; - } - - public String getType() { - return type; - } +public record RecipientType(String type) { @Override public String toString() { diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 49653ca3b5..53ce446881 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -139,7 +139,7 @@ public void onFailure(Exception e) { ownResources -> loadSharedWithResources( resourceIndex, Set.of(user.getName()), - Recipient.USERS.toString(), + Recipient.USERS.getName(), userNameResourcesListener ), listener::onFailure @@ -150,7 +150,7 @@ public void onFailure(Exception e) { userNameResources -> loadSharedWithResources( resourceIndex, user.getSecurityRoles(), - Recipient.ROLES.toString(), + Recipient.ROLES.getName(), rolesResourcesListener ), listener::onFailure @@ -161,7 +161,7 @@ public void onFailure(Exception e) { rolesResources -> loadSharedWithResources( resourceIndex, user.getRoles(), - Recipient.BACKEND_ROLES.toString(), + Recipient.BACKEND_ROLES.getName(), backendRolesResourcesListener ), listener::onFailure diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 45f885349c..4ba251370a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -855,7 +855,9 @@ public void updateResourceSharingInfo( if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { LOGGER.error("User {} is not authorized to share resource {}", requestUserName, resourceId); - throw new ResourceSharingException("User " + requestUserName + " is not authorized to share resource " + resourceId); + listener.onFailure( + new ResourceSharingException("User " + requestUserName + " is not authorized to share resource " + resourceId) + ); } Script updateScript = new Script(ScriptType.INLINE, "painless", """ @@ -1099,14 +1101,16 @@ public void revokeAccess( currentSharingListener.whenComplete(currentSharingInfo -> { // Only admin or the creator of the resource is currently allowed to revoke access if (!isAdmin && currentSharingInfo != null && !currentSharingInfo.getCreatedBy().getCreator().equals(requestUserName)) { - throw new ResourceSharingException( - "User " + requestUserName + " is not authorized to revoke access to resource " + resourceId + listener.onFailure( + new ResourceSharingException( + "User " + requestUserName + " is not authorized to revoke access to resource " + resourceId + ) ); } Map revoke = new HashMap<>(); for (Map.Entry> entry : revokeAccess.entrySet()) { - revoke.put(entry.getKey().getType().toLowerCase(), new ArrayList<>(entry.getValue())); + revoke.put(entry.getKey().type().toLowerCase(), new ArrayList<>(entry.getValue())); } List scopesToUse = (scopes != null) ? new ArrayList<>(scopes) : new ArrayList<>(); diff --git a/src/main/java/org/opensearch/security/resources/SharedWithScope.java b/src/main/java/org/opensearch/security/resources/SharedWithScope.java index 02e3db854f..5a0bbc01b4 100644 --- a/src/main/java/org/opensearch/security/resources/SharedWithScope.java +++ b/src/main/java/org/opensearch/security/resources/SharedWithScope.java @@ -150,7 +150,7 @@ public static ScopeRecipients fromXContent(XContentParser parser) throws IOExcep public void writeTo(StreamOutput out) throws IOException { out.writeMap( recipients, - (streamOutput, recipientType) -> streamOutput.writeString(recipientType.getType()), + (streamOutput, recipientType) -> streamOutput.writeString(recipientType.type()), (streamOutput, strings) -> streamOutput.writeCollection(strings, StreamOutput::writeString) ); } @@ -161,7 +161,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder; } for (Map.Entry> entry : recipients.entrySet()) { - builder.array(entry.getKey().getType(), entry.getValue().toArray()); + builder.array(entry.getKey().type(), entry.getValue().toArray()); } return builder; } diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java index 667f1670dd..355658cf4c 100644 --- a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java +++ b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java @@ -50,7 +50,7 @@ public void writeTo(final StreamOutput out) throws IOException { out.writeString(resourceIndex); out.writeMap( revokeAccess, - (streamOutput, recipientType) -> streamOutput.writeString(recipientType.getType()), + (streamOutput, recipientType) -> streamOutput.writeString(recipientType.type()), StreamOutput::writeStringCollection ); out.writeStringCollection(scopes); diff --git a/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java b/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java index 47151898d1..d1a6854c3e 100644 --- a/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java +++ b/src/test/java/org/opensearch/security/resources/RecipientTypeRegistryTests.java @@ -26,7 +26,7 @@ public void testFromValue() { // Valid Value RecipientType type = RecipientTypeRegistry.fromValue("ble1"); MatcherAssert.assertThat(type, notNullValue()); - MatcherAssert.assertThat(type.getType(), is(equalTo("ble1"))); + MatcherAssert.assertThat(type.type(), is(equalTo("ble1"))); // Invalid Value IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RecipientTypeRegistry.fromValue("bleble")); From 753d5fdc00387860879807e1f6288bec44e68fe1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 04:01:30 -0500 Subject: [PATCH 103/122] Updates the parser to have description as optional for sample resource Signed-off-by: Darshit Chanpura --- .../src/main/java/org/opensearch/sample/SampleResource.java | 2 +- .../actions/transport/UpdateResourceTransportAction.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java index ce123380b4..c1c5a4b66d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java @@ -61,7 +61,7 @@ public SampleResource(StreamInput in) throws IOException { static { PARSER.declareString(constructorArg(), new ParseField("name")); - PARSER.declareString(constructorArg(), new ParseField("description")); + PARSER.declareStringOrNull(constructorArg(), new ParseField("description")); PARSER.declareObjectOrNull(constructorArg(), (p, c) -> p.mapStrings(), null, new ParseField("attributes")); } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java index e9ec0127dd..9dda0f4e4b 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/UpdateResourceTransportAction.java @@ -23,7 +23,9 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.sample.resource.actions.rest.create.*; +import org.opensearch.sample.resource.actions.rest.create.CreateResourceResponse; +import org.opensearch.sample.resource.actions.rest.create.UpdateResourceAction; +import org.opensearch.sample.resource.actions.rest.create.UpdateResourceRequest; import org.opensearch.security.spi.resources.Resource; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; From c6faed8b8acbd5a7905857ed55e12a116af815a0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 04:03:11 -0500 Subject: [PATCH 104/122] Adds integTest for sample-resource-plugin Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePluginTests.java | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java new file mode 100644 index 0000000000..9818382dfd --- /dev/null +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -0,0 +1,311 @@ +package org.opensearch.sample; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.http.HttpStatus; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.opensearch.painless.PainlessModulePlugin; +import org.opensearch.security.OpenSearchSecurityPlugin; +import org.opensearch.security.spi.resources.ResourceAccessScope; +import org.opensearch.security.spi.resources.ResourceProvider; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX; +import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.resources.ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; + +/** + * These tests run with security enabled + */ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class SampleResourcePluginTests { + + public final static TestSecurityConfig.User SHARED_WITH_USER = new TestSecurityConfig.User("resource_sharing_test_user").roles( + new TestSecurityConfig.Role("shared_role").indexPermissions("*").on("*").clusterPermissions("*") + ); + + private static final String SAMPLE_RESOURCE_CREATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/create"; + private static final String SAMPLE_RESOURCE_UPDATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/update"; + private static final String SAMPLE_RESOURCE_DELETE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/delete"; + private static final String SECURITY_RESOURCE_LIST_ENDPOINT = PLUGINS_PREFIX + "/resources/list"; + private static final String SECURITY_RESOURCE_SHARE_ENDPOINT = PLUGINS_PREFIX + "/resources/share"; + private static final String SECURITY_RESOURCE_VERIFY_ENDPOINT = PLUGINS_PREFIX + "/resources/verify_access"; + private static final String SECURITY_RESOURCE_REVOKE_ENDPOINT = PLUGINS_PREFIX + "/resources/revoke"; + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .plugin(SampleResourcePlugin.class, PainlessModulePlugin.class) + .anonymousAuth(true) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_ADMIN, SHARED_WITH_USER) + .build(); + + @Test + public void testPluginInstalledCorrectly() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse pluginsResponse = client.get("_cat/plugins"); + assertThat(pluginsResponse.getBody(), containsString("org.opensearch.security.OpenSearchSecurityPlugin")); + assertThat(pluginsResponse.getBody(), containsString("org.opensearch.sample.SampleResourcePlugin")); + } + } + + @Test + public void testCreateUpdateDeleteSampleResource() throws Exception { + String resourceId; + String resourceSharingDocId; + // create sample resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + String sampleResource = "{\"name\":\"sample\"}"; + HttpResponse response = client.putJson(SAMPLE_RESOURCE_CREATE_ENDPOINT, sampleResource); + response.assertStatusCode(HttpStatus.SC_OK); + + resourceId = response.getTextFromJsonBody("/message").split(":")[1].trim(); + Thread.sleep(2000); + } + + // Create an entry in resource-sharing index + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + // Since test framework doesn't yet allow loading ex tensions we need to create a resource sharing entry manually + String json = String.format( + "{" + + " \"source_idx\": \".sample_resource_sharing_plugin\"," + + " \"resource_id\": \"%s\"," + + " \"created_by\": {" + + " \"user\": \"admin\"" + + " }" + + "}", + resourceId + ); + HttpResponse response = client.postJson(OPENSEARCH_RESOURCE_SHARING_INDEX + "/_doc", json); + assertThat(response.getStatusReason(), containsString("Created")); + resourceSharingDocId = response.bodyAsJsonNode().get("_id").asText(); + // Also update the in-memory map and list + OpenSearchSecurityPlugin.getResourceIndicesMutable().add(RESOURCE_INDEX_NAME); + ResourceProvider provider = new ResourceProvider( + SampleResource.class.getCanonicalName(), + RESOURCE_INDEX_NAME, + new SampleResourceParser() + ); + OpenSearchSecurityPlugin.getResourceProvidersMutable().put(RESOURCE_INDEX_NAME, provider); + + Thread.sleep(1000); + response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); + assertThat(response.getBody(), containsString("sample")); + } + + // Update sample resource (admin should be able to update resource) + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + Thread.sleep(1000); + + String sampleResourceUpdated = "{\"name\":\"sampleUpdated\"}"; + HttpResponse updateResponse = client.postJson(SAMPLE_RESOURCE_UPDATE_ENDPOINT + "/" + resourceId, sampleResourceUpdated); + updateResponse.assertStatusCode(HttpStatus.SC_OK); + } + + // resource should be visible to super-admin + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + + HttpResponse response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); + assertThat(response.getBody(), containsString("sampleUpdated")); + } + + // resource should no longer be visible to shared_with_user + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + + HttpResponse response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(0)); + } + + // shared_with_user should not be able to share admin's resource with itself + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + + String shareWithPayload = "{" + + "\"resource_id\":\"" + + resourceId + + "\"," + + "\"resource_index\":\"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"share_with\":{" + + "\"" + + SampleResourceScope.PUBLIC.value() + + "\":{" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}" + + "}" + + "}"; + HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload); + response.assertStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + assertThat(response.bodyAsJsonNode().toString(), containsString("User " + SHARED_WITH_USER.getName() + " is not authorized")); + // TODO these tests must check for unauthorized instead of internal-server-error + // response.assertStatusCode(HttpStatus.SC_UNAUTHORIZED); + // assertThat(response.bodyAsJsonNode().get("message").asText(), containsString("User is not authorized")); + } + + // share resource with shared_with user + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + Thread.sleep(1000); + String shareWithPayload = "{" + + "\"resource_id\":\"" + + resourceId + + "\"," + + "\"resource_index\":\"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"share_with\":{" + + "\"" + + SampleResourceScope.PUBLIC.value() + + "\":{" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}" + + "}" + + "}"; + HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("message").asText(), containsString(resourceId)); + } + + // resource should now be visible to shared_with_user + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + Thread.sleep(3000); // allow changes to be reflected + HttpResponse response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); + assertThat(response.getBody(), containsString("sampleUpdated")); + } + + // resource is still visible to super-admin + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + + HttpResponse response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); + assertThat(response.getBody(), containsString("sampleUpdated")); + } + + // verify access + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + Thread.sleep(1000); + String verifyAccessPayload = "{\"resource_id\":\"" + + resourceId + + "\",\"resource_index\":\"" + + RESOURCE_INDEX_NAME + + "\",\"scope\":\"" + + ResourceAccessScope.PUBLIC + + "\"}"; + HttpResponse response = client.getWithJsonBody(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.getBody(), containsString("User has requested scope " + ResourceAccessScope.PUBLIC + " access")); + } + + // shared_with user should not be able to revoke access to admin's resource + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + Thread.sleep(1000); + String revokePayload = "{" + + "\"resource_id\": \"" + + resourceId + + "\"," + + "\"resource_index\": \"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"entities\": {" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}," + + "\"scopes\": [\"" + + ResourceAccessScope.PUBLIC + + "\"]" + + "}"; + + HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokePayload); + response.assertStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + assertThat(response.bodyAsJsonNode().toString(), containsString("User " + SHARED_WITH_USER.getName() + " is not authorized")); + // TODO these tests must check for unauthorized instead of internal-server-error + // response.assertStatusCode(HttpStatus.SC_UNAUTHORIZED); + // assertThat(response.bodyAsJsonNode().get("message").asText(), containsString("User is not authorized")); + } + + // revoke share_wit_user's access + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + Thread.sleep(1000); + String revokePayload = "{" + + "\"resource_id\": \"" + + resourceId + + "\"," + + "\"resource_index\": \"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"entities\": {" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}," + + "\"scopes\": [\"" + + ResourceAccessScope.PUBLIC + + "\"]" + + "}"; + + HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokePayload); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().toString(), containsString("Resource " + resourceId + " access revoked successfully.")); + } + + // verify access - share_with_user should no longer have access to admin's resource + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + Thread.sleep(1000); + String verifyAccessPayload = "{\"resource_id\":\"" + + resourceId + + "\",\"resource_index\":\"" + + RESOURCE_INDEX_NAME + + "\",\"scope\":\"" + + ResourceAccessScope.PUBLIC + + "\"}"; + HttpResponse response = client.getWithJsonBody(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.getBody(), containsString("User does not have requested scope " + ResourceAccessScope.PUBLIC + " access")); + } + + // delete sample resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.delete(SAMPLE_RESOURCE_DELETE_ENDPOINT + "/" + resourceId); + response.assertStatusCode(HttpStatus.SC_OK); + Thread.sleep(2000); + } + + // corresponding entry should be removed from resource-sharing index + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + // Since test framework doesn't yet allow loading ex tensions we need to delete the resource sharing entry manually + HttpResponse response = client.delete(OPENSEARCH_RESOURCE_SHARING_INDEX + "/_doc/" + resourceSharingDocId); + assertThat(response.getStatusReason(), containsString("OK")); + + Thread.sleep(1000); + response = client.get(OPENSEARCH_RESOURCE_SHARING_INDEX + "/_search"); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.getBody(), containsString("hits\":[]")); + } + } + + // TODO add test case for updating the resource directly +} From 724b15beb843e67e4151b538ebb3f8d134e9f99c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 04:03:58 -0500 Subject: [PATCH 105/122] Fixes integrationTestRuntimeClasspath task dependency conflict Signed-off-by: Darshit Chanpura --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index ec6a28a50c..7c0d55dc95 100644 --- a/build.gradle +++ b/build.gradle @@ -402,6 +402,7 @@ opensearchplugin { name 'opensearch-security' description 'Provide access control related features for OpenSearch' classname 'org.opensearch.security.OpenSearchSecurityPlugin' + extendedPlugins = ['lang-painless'] } // This requires an additional Jar not published as part of build-tools @@ -503,6 +504,8 @@ configurations { force "org.hamcrest:hamcrest:2.2" force "org.mockito:mockito-core:5.15.2" force "net.bytebuddy:byte-buddy:1.15.11" + force "org.ow2.asm:asm:9.7.1" + force "com.google.j2objc:j2objc-annotations:3.0.0" } } @@ -556,6 +559,7 @@ allprojects { } integrationTestImplementation 'org.slf4j:slf4j-api:2.0.12' integrationTestImplementation 'com.selectivem.collections:special-collections-complete:1.4.0' + integrationTestImplementation "org.opensearch.plugin:lang-painless:${opensearch_version}" } } @@ -628,6 +632,7 @@ check.dependsOn integrationTest dependencies { implementation project(path: ":opensearch-resource-sharing-spi") + implementation "org.opensearch.plugin:lang-painless:${opensearch_version}" implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}" From 2f4ad39f69739ec1ab25875a159d4a6a3d3e26a9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 17:30:49 -0500 Subject: [PATCH 106/122] Finalizes integration test for SampleResourcePlugin Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePluginTests.java | 209 ++++++++++++------ .../org/opensearch/sample/SampleResource.java | 5 +- 2 files changed, 146 insertions(+), 68 deletions(-) diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index 9818382dfd..b5997cac62 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -2,6 +2,7 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; import org.apache.http.HttpStatus; +import org.junit.After; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,6 +54,16 @@ public class SampleResourcePluginTests { .users(USER_ADMIN, SHARED_WITH_USER) .build(); + @After + public void clearIndices() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + client.delete(RESOURCE_INDEX_NAME); + client.delete(OPENSEARCH_RESOURCE_SHARING_INDEX); + OpenSearchSecurityPlugin.getResourceIndicesMutable().remove(RESOURCE_INDEX_NAME); + OpenSearchSecurityPlugin.getResourceProvidersMutable().remove(RESOURCE_INDEX_NAME); + } + } + @Test public void testPluginInstalledCorrectly() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { @@ -73,7 +84,6 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { response.assertStatusCode(HttpStatus.SC_OK); resourceId = response.getTextFromJsonBody("/message").split(":")[1].trim(); - Thread.sleep(2000); } // Create an entry in resource-sharing index @@ -101,16 +111,15 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { ); OpenSearchSecurityPlugin.getResourceProvidersMutable().put(RESOURCE_INDEX_NAME, provider); - Thread.sleep(1000); + Thread.sleep(3000); response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); assertThat(response.getBody(), containsString("sample")); } // Update sample resource (admin should be able to update resource) try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - Thread.sleep(1000); - String sampleResourceUpdated = "{\"name\":\"sampleUpdated\"}"; HttpResponse updateResponse = client.postJson(SAMPLE_RESOURCE_UPDATE_ENDPOINT + "/" + resourceId, sampleResourceUpdated); updateResponse.assertStatusCode(HttpStatus.SC_OK); @@ -164,31 +173,14 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // share resource with shared_with user try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { Thread.sleep(1000); - String shareWithPayload = "{" - + "\"resource_id\":\"" - + resourceId - + "\"," - + "\"resource_index\":\"" - + RESOURCE_INDEX_NAME - + "\"," - + "\"share_with\":{" - + "\"" - + SampleResourceScope.PUBLIC.value() - + "\":{" - + "\"users\": [\"" - + SHARED_WITH_USER.getName() - + "\"]" - + "}" - + "}" - + "}"; - HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload); + + HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().get("message").asText(), containsString(resourceId)); } // resource should now be visible to shared_with_user try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { - Thread.sleep(3000); // allow changes to be reflected HttpResponse response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); @@ -197,7 +189,6 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // resource is still visible to super-admin try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { - HttpResponse response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); @@ -206,7 +197,6 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // verify access try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { - Thread.sleep(1000); String verifyAccessPayload = "{\"resource_id\":\"" + resourceId + "\",\"resource_index\":\"" @@ -221,25 +211,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // shared_with user should not be able to revoke access to admin's resource try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { - Thread.sleep(1000); - String revokePayload = "{" - + "\"resource_id\": \"" - + resourceId - + "\"," - + "\"resource_index\": \"" - + RESOURCE_INDEX_NAME - + "\"," - + "\"entities\": {" - + "\"users\": [\"" - + SHARED_WITH_USER.getName() - + "\"]" - + "}," - + "\"scopes\": [\"" - + ResourceAccessScope.PUBLIC - + "\"]" - + "}"; - - HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokePayload); + HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokeAccessPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); assertThat(response.bodyAsJsonNode().toString(), containsString("User " + SHARED_WITH_USER.getName() + " is not authorized")); // TODO these tests must check for unauthorized instead of internal-server-error @@ -247,34 +219,15 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // assertThat(response.bodyAsJsonNode().get("message").asText(), containsString("User is not authorized")); } - // revoke share_wit_user's access + // revoke share_with_user's access try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - Thread.sleep(1000); - String revokePayload = "{" - + "\"resource_id\": \"" - + resourceId - + "\"," - + "\"resource_index\": \"" - + RESOURCE_INDEX_NAME - + "\"," - + "\"entities\": {" - + "\"users\": [\"" - + SHARED_WITH_USER.getName() - + "\"]" - + "}," - + "\"scopes\": [\"" - + ResourceAccessScope.PUBLIC - + "\"]" - + "}"; - - HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokePayload); + HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokeAccessPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().toString(), containsString("Resource " + resourceId + " access revoked successfully.")); } // verify access - share_with_user should no longer have access to admin's resource try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { - Thread.sleep(1000); String verifyAccessPayload = "{\"resource_id\":\"" + resourceId + "\",\"resource_index\":\"" @@ -291,7 +244,6 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { HttpResponse response = client.delete(SAMPLE_RESOURCE_DELETE_ENDPOINT + "/" + resourceId); response.assertStatusCode(HttpStatus.SC_OK); - Thread.sleep(2000); } // corresponding entry should be removed from resource-sharing index @@ -308,4 +260,129 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { } // TODO add test case for updating the resource directly + @Test + public void testDLSRestrictionForResourceByDirectlyUpdatingTheResourceIndex() throws Exception { + String resourceId; + // create sample resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + String sampleResource = "{\"name\":\"sample\"}"; + HttpResponse response = client.postJson(RESOURCE_INDEX_NAME + "/_doc", sampleResource); + response.assertStatusCode(HttpStatus.SC_CREATED); + + resourceId = response.bodyAsJsonNode().get("_id").asText(); + } + + // Create an entry in resource-sharing index + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + // Since test framework doesn't yet allow loading ex tensions we need to create a resource sharing entry manually + String json = String.format( + "{" + + " \"source_idx\": \".sample_resource_sharing_plugin\"," + + " \"resource_id\": \"%s\"," + + " \"created_by\": {" + + " \"user\": \"admin\"" + + " }" + + "}", + resourceId + ); + HttpResponse response = client.postJson(OPENSEARCH_RESOURCE_SHARING_INDEX + "/_doc", json); + assertThat(response.getStatusReason(), containsString("Created")); + // Also update the in-memory map and list + OpenSearchSecurityPlugin.getResourceIndicesMutable().add(RESOURCE_INDEX_NAME); + ResourceProvider provider = new ResourceProvider( + SampleResource.class.getCanonicalName(), + RESOURCE_INDEX_NAME, + new SampleResourceParser() + ); + OpenSearchSecurityPlugin.getResourceProvidersMutable().put(RESOURCE_INDEX_NAME, provider); + + Thread.sleep(1000); + } + + // resource is still visible to super-admin + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + + HttpResponse response = client.postJson(RESOURCE_INDEX_NAME + "/_search", "{\"query\" : {\"match_all\" : {}}}"); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("hits").get("hits").size(), equalTo(1)); + assertThat(response.getBody(), containsString("sample")); + } + + String updatePayload = "{" + "\"doc\": {" + "\"name\": \"sampleUpdated\"" + "}" + "}"; + + // Update sample resource with shared_with user. This will fail since the resource has not been shared + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + HttpResponse updateResponse = client.postJson(RESOURCE_INDEX_NAME + "/_update/" + resourceId, updatePayload); + // it will show not found since the resource is not visible to shared_with_user + updateResponse.assertStatusCode(HttpStatus.SC_NOT_FOUND); + } + + // Admin is still allowed to update its own resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse updateResponse = client.postJson(RESOURCE_INDEX_NAME + "/_update/" + resourceId, updatePayload); + // it will show not found since the resource is not visible to shared_with_user + updateResponse.assertStatusCode(HttpStatus.SC_OK); + assertThat(updateResponse.bodyAsJsonNode().get("_shards").get("successful").asInt(), equalTo(1)); + } + + // Verify that share_with user does not have access to the resource + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + HttpResponse getResponse = client.get(RESOURCE_INDEX_NAME + "/_doc/" + resourceId); + // it will show not found since the resource is not visible to shared_with_user + getResponse.assertStatusCode(HttpStatus.SC_NOT_FOUND); + } + + // share the resource with shared_with_user + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload(resourceId)); + response.assertStatusCode(HttpStatus.SC_OK); + } + + // Verify that share_with user now has access to the resource + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + HttpResponse getResponse = client.get(RESOURCE_INDEX_NAME + "/_doc/" + resourceId); + // it will show not found since the resource is not visible to shared_with_user + getResponse.assertStatusCode(HttpStatus.SC_OK); + assertThat(getResponse.getBody(), containsString("sampleUpdated")); + } + } + + private static String shareWithPayload(String resourceId) { + return "{" + + "\"resource_id\":\"" + + resourceId + + "\"," + + "\"resource_index\":\"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"share_with\":{" + + "\"" + + SampleResourceScope.PUBLIC.value() + + "\":{" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}" + + "}" + + "}"; + } + + private static String revokeAccessPayload(String resourceId) { + return "{" + + "\"resource_id\": \"" + + resourceId + + "\"," + + "\"resource_index\": \"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"entities\": {" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}," + + "\"scopes\": [\"" + + ResourceAccessScope.PUBLIC + + "\"]" + + "}"; + } } diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java index c1c5a4b66d..23aae25d42 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/SampleResource.java @@ -23,6 +23,7 @@ import org.opensearch.security.spi.resources.Resource; import static org.opensearch.core.xcontent.ConstructingObjectParser.constructorArg; +import static org.opensearch.core.xcontent.ConstructingObjectParser.optionalConstructorArg; public class SampleResource extends Resource { @@ -61,8 +62,8 @@ public SampleResource(StreamInput in) throws IOException { static { PARSER.declareString(constructorArg(), new ParseField("name")); - PARSER.declareStringOrNull(constructorArg(), new ParseField("description")); - PARSER.declareObjectOrNull(constructorArg(), (p, c) -> p.mapStrings(), null, new ParseField("attributes")); + PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("description")); + PARSER.declareObjectOrNull(optionalConstructorArg(), (p, c) -> p.mapStrings(), null, new ParseField("attributes")); } public static SampleResource fromXContent(XContentParser parser) throws IOException { From 41eb9517a0a9fbcd8118e74d4993a72676ed0f08 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 17:37:58 -0500 Subject: [PATCH 107/122] Adds test retry logic for sample-sharing-plugin Signed-off-by: Darshit Chanpura --- sample-resource-plugin/build.gradle | 13 +++++++++++++ .../sample/SampleResourcePluginTests.java | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle index 6ceca2704c..e3c057cf3f 100644 --- a/sample-resource-plugin/build.gradle +++ b/sample-resource-plugin/build.gradle @@ -3,6 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +plugins { + id "org.gradle.test-retry" version "1.6.0" +} apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.testclusters' @@ -48,6 +51,7 @@ ext { } } + repositories { mavenLocal() mavenCentral() @@ -93,6 +97,15 @@ sourceSets { } tasks.register("integrationTest", Test) { + doFirst { + if (System.getenv('DISABLE_RETRY') != 'true') { + retry { + failOnPassedAfterRetry = false + maxRetries = 2 + maxFailures = 5 + } + } + } description = 'Run integration tests for the subproject.' group = 'verification' diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index b5997cac62..9f7a1f503f 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -111,7 +111,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { ); OpenSearchSecurityPlugin.getResourceProvidersMutable().put(RESOURCE_INDEX_NAME, provider); - Thread.sleep(3000); + Thread.sleep(1000); response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().get("resources").size(), equalTo(1)); @@ -221,6 +221,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // revoke share_with_user's access try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + Thread.sleep(1000); HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokeAccessPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.bodyAsJsonNode().toString(), containsString("Resource " + resourceId + " access revoked successfully.")); From 1d2642375a20bfa004f6d3d0bdd1f4846d4bbe1f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 17:42:18 -0500 Subject: [PATCH 108/122] Adds CI workflow that runs sample-plugin integration tests Signed-off-by: Darshit Chanpura --- .../integration-tests-sample-plugin.yml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/integration-tests-sample-plugin.yml diff --git a/.github/workflows/integration-tests-sample-plugin.yml b/.github/workflows/integration-tests-sample-plugin.yml new file mode 100644 index 0000000000..9ea0fb92f3 --- /dev/null +++ b/.github/workflows/integration-tests-sample-plugin.yml @@ -0,0 +1,35 @@ +name: Bulk Integration Test For Sample Resource Plugin + +on: [workflow_dispatch] + +env: + GRADLE_OPTS: -Dhttp.keepAlive=false + +jobs: + bulk-integration-test-run: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + jdk: [21] + + steps: + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: ${{ matrix.jdk }} + + - uses: actions/checkout@v4 + + - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew :sample-resource-sharing-plugin:integrationTest + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ matrix.jdk }}-${{ matrix.test-file }}-sample-resource-sharing-reports + path: | + ./sample-resource-sharing-plugin/build/reports/ + + - name: check archive for debugging + if: always() + run: echo "Check the artifact ${{ matrix.jdk }}-${{ matrix.test-file }}-sample-resource-sharing-reports.zip for detailed test results" From 2b7239ae4cb1ba74032f9d1ce801e659320d164e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 17:52:20 -0500 Subject: [PATCH 109/122] Change painless dependency to compileOnly Signed-off-by: Darshit Chanpura --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7c0d55dc95..1e686e0605 100644 --- a/build.gradle +++ b/build.gradle @@ -632,7 +632,7 @@ check.dependsOn integrationTest dependencies { implementation project(path: ":opensearch-resource-sharing-spi") - implementation "org.opensearch.plugin:lang-painless:${opensearch_version}" + compileOnly "org.opensearch.plugin:lang-painless:${opensearch_version}" implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}" From 7418bcb7c44f72a2fabfa86f9da39e6b87def02c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 20:48:52 -0500 Subject: [PATCH 110/122] Attempts to fix CI Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 47 +++++++++++++++---- .../integration-tests-sample-plugin.yml | 2 +- spi/build.gradle | 2 +- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a41062883..c8a442f2ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -211,16 +211,14 @@ jobs: - run: ./gradlew clean assemble - uses: github/codeql-action/analyze@v3 - build-artifact-names: + build-version-qualifier: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 with: - distribution: temurin # Temurin is a distribution of adoptium + distribution: temurin java-version: 21 - - run: | security_plugin_version=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}') security_plugin_version_no_snapshot=$(echo $security_plugin_version | sed 's/-SNAPSHOT//g') @@ -238,15 +236,46 @@ jobs: echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }} echo ${{ env.TEST_QUALIFIER }} - - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip + publish-spi: + name: Publish SPI + runs-on: ubuntu-latest + steps: + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + + - name: Checkout SPI + uses: actions/checkout@v4 + + - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal + + - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false + + - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} + + - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} + + build-artifact-names: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: 21 + + - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION }}.zip - - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip + - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip && test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip - - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip + - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip && && test -s ./build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip - - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip + - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip && test -s ./build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip - - run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom + - run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION }}.pom - name: List files in the build directory if there was an error run: ls -al ./build/distributions/ diff --git a/.github/workflows/integration-tests-sample-plugin.yml b/.github/workflows/integration-tests-sample-plugin.yml index 9ea0fb92f3..e148c528de 100644 --- a/.github/workflows/integration-tests-sample-plugin.yml +++ b/.github/workflows/integration-tests-sample-plugin.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v4 - - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew :sample-resource-sharing-plugin:integrationTest + - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew :opensearch-sample-resource-plugin:integrationTest - uses: actions/upload-artifact@v4 if: always() diff --git a/spi/build.gradle b/spi/build.gradle index b2db11979f..f23861c448 100644 --- a/spi/build.gradle +++ b/spi/build.gradle @@ -69,7 +69,7 @@ publishing { } repositories { maven { - name = "Snapshots" // optional target repository name + name = "Snapshots" url = "https://aws.oss.sonatype.org/content/repositories/snapshots" credentials { username "$System.env.SONATYPE_USERNAME" From a9763e91854a74bbbcf67d7b9a82bfb6482e9d9b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 21:46:52 -0500 Subject: [PATCH 111/122] Changes verify to post Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePluginTests.java | 18 ++---------------- .../verify/RestVerifyResourceAccessAction.java | 4 ++-- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index 9f7a1f503f..b2d1c68aac 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -11,7 +11,6 @@ import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.spi.resources.ResourceAccessScope; import org.opensearch.security.spi.resources.ResourceProvider; -import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; @@ -20,9 +19,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.sample.AbstractSampleResourcePluginTests.*; import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; -import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX; -import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.resources.ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; @@ -32,19 +30,7 @@ */ @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) -public class SampleResourcePluginTests { - - public final static TestSecurityConfig.User SHARED_WITH_USER = new TestSecurityConfig.User("resource_sharing_test_user").roles( - new TestSecurityConfig.Role("shared_role").indexPermissions("*").on("*").clusterPermissions("*") - ); - - private static final String SAMPLE_RESOURCE_CREATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/create"; - private static final String SAMPLE_RESOURCE_UPDATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/update"; - private static final String SAMPLE_RESOURCE_DELETE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/delete"; - private static final String SECURITY_RESOURCE_LIST_ENDPOINT = PLUGINS_PREFIX + "/resources/list"; - private static final String SECURITY_RESOURCE_SHARE_ENDPOINT = PLUGINS_PREFIX + "/resources/share"; - private static final String SECURITY_RESOURCE_VERIFY_ENDPOINT = PLUGINS_PREFIX + "/resources/verify_access"; - private static final String SECURITY_RESOURCE_REVOKE_ENDPOINT = PLUGINS_PREFIX + "/resources/revoke"; +public class SampleResourcePluginWithSecurityTests { @ClassRule public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java index 3a7e713a83..d8678c7c19 100644 --- a/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java +++ b/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java @@ -20,7 +20,7 @@ import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; -import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; @@ -30,7 +30,7 @@ public RestVerifyResourceAccessAction() {} @Override public List routes() { - return addRoutesPrefix(ImmutableList.of(new Route(GET, "/resources/verify_access")), PLUGIN_ROUTE_PREFIX); + return addRoutesPrefix(ImmutableList.of(new Route(POST, "/resources/verify_access")), PLUGIN_ROUTE_PREFIX); } @Override From 6918b28e480213dcab803b871fe051716c38d071 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 21:47:24 -0500 Subject: [PATCH 112/122] Adds a noop integration test when resource-sharing feature is disabled Signed-off-by: Darshit Chanpura --- .../AbstractSampleResourcePluginTests.java | 70 +++++++++ ...rcePluginResourceSharingDisabledTests.java | 134 ++++++++++++++++++ .../sample/SampleResourcePluginTests.java | 66 +-------- 3 files changed, 209 insertions(+), 61 deletions(-) create mode 100644 sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java create mode 100644 sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java new file mode 100644 index 0000000000..255e27ba53 --- /dev/null +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java @@ -0,0 +1,70 @@ +package org.opensearch.sample; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.runner.RunWith; + +import org.opensearch.security.spi.resources.ResourceAccessScope; +import org.opensearch.test.framework.TestSecurityConfig; + +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; +import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX; +import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; + +/** + * These tests run with security enabled + */ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class AbstractSampleResourcePluginTests { + + final static TestSecurityConfig.User SHARED_WITH_USER = new TestSecurityConfig.User("resource_sharing_test_user").roles( + new TestSecurityConfig.Role("shared_role").indexPermissions("*").on("*").clusterPermissions("*") + ); + + static final String SAMPLE_RESOURCE_CREATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/create"; + static final String SAMPLE_RESOURCE_UPDATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/update"; + static final String SAMPLE_RESOURCE_DELETE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/delete"; + static final String SECURITY_RESOURCE_LIST_ENDPOINT = PLUGINS_PREFIX + "/resources/list"; + static final String SECURITY_RESOURCE_SHARE_ENDPOINT = PLUGINS_PREFIX + "/resources/share"; + static final String SECURITY_RESOURCE_VERIFY_ENDPOINT = PLUGINS_PREFIX + "/resources/verify_access"; + static final String SECURITY_RESOURCE_REVOKE_ENDPOINT = PLUGINS_PREFIX + "/resources/revoke"; + + static String shareWithPayload(String resourceId) { + return "{" + + "\"resource_id\":\"" + + resourceId + + "\"," + + "\"resource_index\":\"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"share_with\":{" + + "\"" + + SampleResourceScope.PUBLIC.value() + + "\":{" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}" + + "}" + + "}"; + } + + static String revokeAccessPayload(String resourceId) { + return "{" + + "\"resource_id\": \"" + + resourceId + + "\"," + + "\"resource_index\": \"" + + RESOURCE_INDEX_NAME + + "\"," + + "\"entities\": {" + + "\"users\": [\"" + + SHARED_WITH_USER.getName() + + "\"]" + + "}," + + "\"scopes\": [\"" + + ResourceAccessScope.PUBLIC + + "\"]" + + "}"; + } +} diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java new file mode 100644 index 0000000000..fd7158f1e4 --- /dev/null +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java @@ -0,0 +1,134 @@ +package org.opensearch.sample; + +import java.util.Map; + +import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.apache.http.HttpStatus; +import org.junit.*; +import org.junit.runner.RunWith; + +import org.opensearch.painless.PainlessModulePlugin; +import org.opensearch.test.framework.cluster.ClusterManager; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; +import static org.opensearch.security.resources.ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; +import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED; +import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; +import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; + +/** + * These tests run with security disabled + */ +@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) +@ThreadLeakScope(ThreadLeakScope.Scope.NONE) +public class SampleResourcePluginResourceSharingDisabledTests extends AbstractSampleResourcePluginTests { + + @ClassRule + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) + .plugin(SampleResourcePlugin.class, PainlessModulePlugin.class) + .anonymousAuth(true) + .authc(AUTHC_HTTPBASIC_INTERNAL) + .users(USER_ADMIN, SHARED_WITH_USER) + .nodeSettings(Map.of(OPENSEARCH_RESOURCE_SHARING_ENABLED, false)) + .build(); + + @After + public void clearIndices() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + client.delete(RESOURCE_INDEX_NAME); + } + } + + @Test + public void testPluginInstalledCorrectly() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse pluginsResponse = client.get("_cat/plugins"); + assertThat(pluginsResponse.getBody(), containsString("org.opensearch.security.OpenSearchSecurityPlugin")); + assertThat(pluginsResponse.getBody(), containsString("org.opensearch.sample.SampleResourcePlugin")); + } + } + + @Test + public void testNoResourceRestrictions() throws Exception { + String resourceId; + // create sample resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + String sampleResource = "{\"name\":\"sample\"}"; + HttpResponse response = client.putJson(SAMPLE_RESOURCE_CREATE_ENDPOINT, sampleResource); + response.assertStatusCode(HttpStatus.SC_OK); + + resourceId = response.getTextFromJsonBody("/message").split(":")[1].trim(); + } + + // assert that resource-sharing index doesn't exist and neither do resource-sharing APIs + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + HttpResponse response = client.get(OPENSEARCH_RESOURCE_SHARING_INDEX + "/_search"); + response.assertStatusCode(HttpStatus.SC_NOT_FOUND); + + response = client.get(SECURITY_RESOURCE_LIST_ENDPOINT + "/" + RESOURCE_INDEX_NAME); + response.assertStatusCode(HttpStatus.SC_BAD_REQUEST); + assertThat(response.getBody(), containsString("no handler found for uri")); + + response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, "{}"); + response.assertStatusCode(HttpStatus.SC_BAD_REQUEST); + assertThat(response.getBody(), containsString("no handler found for uri")); + + response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, "{}"); + response.assertStatusCode(HttpStatus.SC_BAD_REQUEST); + assertThat(response.getBody(), containsString("no handler found for uri")); + + response = client.postJson(SECURITY_RESOURCE_VERIFY_ENDPOINT, "{}"); + response.assertStatusCode(HttpStatus.SC_BAD_REQUEST); + assertThat(response.getBody(), containsString("no handler found for uri")); + } + + // resource should be visible to super-admin + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + + HttpResponse response = client.postJson(RESOURCE_INDEX_NAME + "/_search", "{\"query\" : {\"match_all\" : {}}}"); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("hits").get("hits").size(), equalTo(1)); + assertThat(response.getBody(), containsString("sample")); + } + + // resource should be visible to shared_with_user since there is no restriction and this user has * permission + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + HttpResponse response = client.postJson(RESOURCE_INDEX_NAME + "/_search", "{\"query\" : {\"match_all\" : {}}}"); + response.assertStatusCode(HttpStatus.SC_OK); + assertThat(response.bodyAsJsonNode().get("hits").get("hits").size(), equalTo(1)); + } + + // shared_with_user is able to update admin's resource + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + String updatePayload = "{" + "\"name\": \"sampleUpdated\"" + "}"; + HttpResponse updateResponse = client.postJson(SAMPLE_RESOURCE_UPDATE_ENDPOINT + "/" + resourceId, updatePayload); + updateResponse.assertStatusCode(HttpStatus.SC_OK); + } + + // admin can see updated value + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse getResponse = client.get(RESOURCE_INDEX_NAME + "/_doc/" + resourceId); + getResponse.assertStatusCode(HttpStatus.SC_OK); + assertThat(getResponse.getBody(), containsString("sampleUpdated")); + } + + // delete sample resource - share_with user delete admin user's resource + try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { + HttpResponse response = client.delete(SAMPLE_RESOURCE_DELETE_ENDPOINT + "/" + resourceId); + response.assertStatusCode(HttpStatus.SC_OK); + } + + // admin can no longer see the resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse getResponse = client.get(RESOURCE_INDEX_NAME + "/_doc/" + resourceId); + getResponse.assertStatusCode(HttpStatus.SC_NOT_FOUND); + } + + } +} diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index b2d1c68aac..63e6ad09aa 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -26,11 +26,11 @@ import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; /** - * These tests run with security enabled + * These tests run with resource sharing enabled */ @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) @ThreadLeakScope(ThreadLeakScope.Scope.NONE) -public class SampleResourcePluginWithSecurityTests { +public class SampleResourcePluginTests extends AbstractSampleResourcePluginTests { @ClassRule public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) @@ -131,24 +131,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { // shared_with_user should not be able to share admin's resource with itself try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { - String shareWithPayload = "{" - + "\"resource_id\":\"" - + resourceId - + "\"," - + "\"resource_index\":\"" - + RESOURCE_INDEX_NAME - + "\"," - + "\"share_with\":{" - + "\"" - + SampleResourceScope.PUBLIC.value() - + "\":{" - + "\"users\": [\"" - + SHARED_WITH_USER.getName() - + "\"]" - + "}" - + "}" - + "}"; - HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload); + HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); assertThat(response.bodyAsJsonNode().toString(), containsString("User " + SHARED_WITH_USER.getName() + " is not authorized")); // TODO these tests must check for unauthorized instead of internal-server-error @@ -190,7 +173,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { + "\",\"scope\":\"" + ResourceAccessScope.PUBLIC + "\"}"; - HttpResponse response = client.getWithJsonBody(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); + HttpResponse response = client.postJson(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.getBody(), containsString("User has requested scope " + ResourceAccessScope.PUBLIC + " access")); } @@ -222,7 +205,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { + "\",\"scope\":\"" + ResourceAccessScope.PUBLIC + "\"}"; - HttpResponse response = client.getWithJsonBody(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); + HttpResponse response = client.postJson(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); response.assertStatusCode(HttpStatus.SC_OK); assertThat(response.getBody(), containsString("User does not have requested scope " + ResourceAccessScope.PUBLIC + " access")); } @@ -246,7 +229,6 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { } } - // TODO add test case for updating the resource directly @Test public void testDLSRestrictionForResourceByDirectlyUpdatingTheResourceIndex() throws Exception { String resourceId; @@ -334,42 +316,4 @@ public void testDLSRestrictionForResourceByDirectlyUpdatingTheResourceIndex() th } } - private static String shareWithPayload(String resourceId) { - return "{" - + "\"resource_id\":\"" - + resourceId - + "\"," - + "\"resource_index\":\"" - + RESOURCE_INDEX_NAME - + "\"," - + "\"share_with\":{" - + "\"" - + SampleResourceScope.PUBLIC.value() - + "\":{" - + "\"users\": [\"" - + SHARED_WITH_USER.getName() - + "\"]" - + "}" - + "}" - + "}"; - } - - private static String revokeAccessPayload(String resourceId) { - return "{" - + "\"resource_id\": \"" - + resourceId - + "\"," - + "\"resource_index\": \"" - + RESOURCE_INDEX_NAME - + "\"," - + "\"entities\": {" - + "\"users\": [\"" - + SHARED_WITH_USER.getName() - + "\"]" - + "}," - + "\"scopes\": [\"" - + ResourceAccessScope.PUBLIC - + "\"]" - + "}"; - } } From 456418e535076d2658123a878c99b8bfaafe84a8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sat, 18 Jan 2025 22:19:58 -0500 Subject: [PATCH 113/122] Fixes Github workflow Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 137 +++++++++--------- .../integration-tests-sample-plugin.yml | 35 ----- build.gradle | 3 + 3 files changed, 75 insertions(+), 100 deletions(-) delete mode 100644 .github/workflows/integration-tests-sample-plugin.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8a442f2ee..6300c70f89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,13 +111,26 @@ jobs: - name: Checkout security uses: actions/checkout@v4 - - name: Build and Test + - name: Run Integration Tests uses: gradle/gradle-build-action@v3 with: cache-disabled: true arguments: | integrationTest -Dbuild.snapshot=false + - name: Publish SPI to Local Maven + uses: gradle/gradle-build-action@v3 + with: + cache-disabled: true + arguments: :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false + + - name: Run SampleResourcePlugin Integration Tests + uses: gradle/gradle-build-action@v3 + with: + cache-disabled: true + arguments: | + :opensearch-sample-resource-plugin:integrationTest -Dbuild.snapshot=false + - uses: actions/upload-artifact@v4 if: always() with: @@ -125,7 +138,6 @@ jobs: path: | ./build/reports/ - resource-tests: env: CI_ENVIRONMENT: resource-test @@ -146,7 +158,7 @@ jobs: - name: Checkout security uses: actions/checkout@v4 - - name: Build and Test + - name: Run Resource Tests uses: gradle/gradle-build-action@v3 with: cache-disabled: true @@ -211,72 +223,67 @@ jobs: - run: ./gradlew clean assemble - uses: github/codeql-action/analyze@v3 - build-version-qualifier: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 21 - - run: | - security_plugin_version=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}') - security_plugin_version_no_snapshot=$(echo $security_plugin_version | sed 's/-SNAPSHOT//g') - security_plugin_version_only_number=$(echo $security_plugin_version_no_snapshot | cut -d- -f1) - test_qualifier=alpha2 - - echo "SECURITY_PLUGIN_VERSION=$security_plugin_version" >> $GITHUB_ENV - echo "SECURITY_PLUGIN_VERSION_NO_SNAPSHOT=$security_plugin_version_no_snapshot" >> $GITHUB_ENV - echo "SECURITY_PLUGIN_VERSION_ONLY_NUMBER=$security_plugin_version_only_number" >> $GITHUB_ENV - echo "TEST_QUALIFIER=$test_qualifier" >> $GITHUB_ENV - - - run: | - echo ${{ env.SECURITY_PLUGIN_VERSION }} - echo ${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }} - echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }} - echo ${{ env.TEST_QUALIFIER }} - - publish-spi: - name: Publish SPI + build-artifact-names: runs-on: ubuntu-latest steps: - - name: Set up JDK + - name: Setup Environment + uses: actions/checkout@v4 + + - name: Configure Java uses: actions/setup-java@v4 with: distribution: temurin java-version: 21 - - name: Checkout SPI - uses: actions/checkout@v4 - - - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal - - - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false - - - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} - - - run: ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} - - build-artifact-names: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-java@v4 - with: - distribution: temurin # Temurin is a distribution of adoptium - java-version: 21 - - - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION }}.zip - - - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip && test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip - - - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip && && test -s ./build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip - - - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip && test -s ./build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip - - - run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-sample-resource-plugin-${{ env.SECURITY_PLUGIN_VERSION }}.pom - - - name: List files in the build directory if there was an error - run: ls -al ./build/distributions/ - if: failure() + - name: Build and Test Artifacts + run: | + # Set version variables + security_plugin_version=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}') + security_plugin_version_no_snapshot=$(echo $security_plugin_version | sed 's/-SNAPSHOT//g') + security_plugin_version_only_number=$(echo $security_plugin_version_no_snapshot | cut -d- -f1) + test_qualifier=alpha2 + + # Export variables to GitHub Environment + echo "SECURITY_PLUGIN_VERSION=$security_plugin_version" >> $GITHUB_ENV + echo "SECURITY_PLUGIN_VERSION_NO_SNAPSHOT=$security_plugin_version_no_snapshot" >> $GITHUB_ENV + echo "SECURITY_PLUGIN_VERSION_ONLY_NUMBER=$security_plugin_version_only_number" >> $GITHUB_ENV + echo "TEST_QUALIFIER=$test_qualifier" >> $GITHUB_ENV + + # Debug print versions + echo "Versions:" + echo $security_plugin_version + echo $security_plugin_version_no_snapshot + echo $security_plugin_version_only_number + echo $test_qualifier + + # Publish SPI + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=$test_qualifier + + # Build artifacts + ./gradlew clean assemble && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version.zip && \ + test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version.zip + + ./gradlew clean assemble -Dbuild.snapshot=false && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version_no_snapshot.zip && \ + test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version_no_snapshot.zip + + ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version_only_number-$test_qualifier.zip && \ + test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version_only_number-$test_qualifier.zip + + ./gradlew clean assemble -Dbuild.version_qualifier=$test_qualifier && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT.zip && \ + test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT.zip + + ./gradlew clean publishPluginZipPublicationToZipStagingRepository && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version.zip && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version.pom && \ + test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version.pom + + - name: List files in build directory on failure + if: failure() + run: ls -al ./build/distributions/ diff --git a/.github/workflows/integration-tests-sample-plugin.yml b/.github/workflows/integration-tests-sample-plugin.yml deleted file mode 100644 index e148c528de..0000000000 --- a/.github/workflows/integration-tests-sample-plugin.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Bulk Integration Test For Sample Resource Plugin - -on: [workflow_dispatch] - -env: - GRADLE_OPTS: -Dhttp.keepAlive=false - -jobs: - bulk-integration-test-run: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - jdk: [21] - - steps: - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: ${{ matrix.jdk }} - - - uses: actions/checkout@v4 - - - run: OPENDISTRO_SECURITY_TEST_OPENSSL_OPT=true ./gradlew :opensearch-sample-resource-plugin:integrationTest - - - uses: actions/upload-artifact@v4 - if: always() - with: - name: ${{ matrix.jdk }}-${{ matrix.test-file }}-sample-resource-sharing-reports - path: | - ./sample-resource-sharing-plugin/build/reports/ - - - name: check archive for debugging - if: always() - run: echo "Check the artifact ${{ matrix.jdk }}-${{ matrix.test-file }}-sample-resource-sharing-reports.zip for detailed test results" diff --git a/build.gradle b/build.gradle index 1e686e0605..8319dd0f0b 100644 --- a/build.gradle +++ b/build.gradle @@ -583,6 +583,9 @@ sourceSets { //add new task that runs integration tests task integrationTest(type: Test) { + filter { + excludeTestsMatching 'org.opensearch.sample.*ResourcePlugin*' + } doFirst { // Only run resources tests on resource-test CI environments or locally if (System.getenv('CI_ENVIRONMENT') != 'resource-test' && System.getenv('CI_ENVIRONMENT') != null) { From ba6928a25c77e3a6af10be3a7491d77ad27289d9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 00:35:45 -0500 Subject: [PATCH 114/122] Consolidates rest actions to a single place Signed-off-by: Darshit Chanpura --- .../AbstractSampleResourcePluginTests.java | 11 +- ...rcePluginResourceSharingDisabledTests.java | 4 +- .../sample/SampleResourcePluginTests.java | 1 - .../security/OpenSearchSecurityPlugin.java | 14 +- .../security/dlic/rest/support/Utils.java | 2 + .../access/RestResourceAccessAction.java | 168 ++++++++++++++++++ .../RestListAccessibleResourcesAction.java | 49 ----- .../RestRevokeResourceAccessAction.java | 74 -------- .../access/share/RestShareResourceAction.java | 79 -------- .../RestVerifyResourceAccessAction.java | 59 ------ 10 files changed, 181 insertions(+), 280 deletions(-) create mode 100644 src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java index 255e27ba53..6435c371ab 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/AbstractSampleResourcePluginTests.java @@ -8,7 +8,7 @@ import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX; -import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_RESOURCE_ROUTE_PREFIX; /** * These tests run with security enabled @@ -24,10 +24,11 @@ public class AbstractSampleResourcePluginTests { static final String SAMPLE_RESOURCE_CREATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/create"; static final String SAMPLE_RESOURCE_UPDATE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/update"; static final String SAMPLE_RESOURCE_DELETE_ENDPOINT = SAMPLE_RESOURCE_PLUGIN_PREFIX + "/delete"; - static final String SECURITY_RESOURCE_LIST_ENDPOINT = PLUGINS_PREFIX + "/resources/list"; - static final String SECURITY_RESOURCE_SHARE_ENDPOINT = PLUGINS_PREFIX + "/resources/share"; - static final String SECURITY_RESOURCE_VERIFY_ENDPOINT = PLUGINS_PREFIX + "/resources/verify_access"; - static final String SECURITY_RESOURCE_REVOKE_ENDPOINT = PLUGINS_PREFIX + "/resources/revoke"; + private static final String PLUGIN_RESOURCE_ROUTE_PREFIX_NO_LEADING_SLASH = PLUGIN_RESOURCE_ROUTE_PREFIX.replaceFirst("/", ""); + static final String SECURITY_RESOURCE_LIST_ENDPOINT = PLUGIN_RESOURCE_ROUTE_PREFIX_NO_LEADING_SLASH + "/list"; + static final String SECURITY_RESOURCE_SHARE_ENDPOINT = PLUGIN_RESOURCE_ROUTE_PREFIX_NO_LEADING_SLASH + "/share"; + static final String SECURITY_RESOURCE_VERIFY_ENDPOINT = PLUGIN_RESOURCE_ROUTE_PREFIX_NO_LEADING_SLASH + "/verify_access"; + static final String SECURITY_RESOURCE_REVOKE_ENDPOINT = PLUGIN_RESOURCE_ROUTE_PREFIX_NO_LEADING_SLASH + "/revoke"; static String shareWithPayload(String resourceId) { return "{" diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java index fd7158f1e4..62ba2e343c 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginResourceSharingDisabledTests.java @@ -4,7 +4,9 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; import org.apache.http.HttpStatus; -import org.junit.*; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Test; import org.junit.runner.RunWith; import org.opensearch.painless.PainlessModulePlugin; diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index 63e6ad09aa..f34cf0c561 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -19,7 +19,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.opensearch.sample.AbstractSampleResourcePluginTests.*; import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; import static org.opensearch.security.resources.ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 87a2fb4989..5b3ebbd635 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -190,13 +190,10 @@ import org.opensearch.security.rest.SecurityInfoAction; import org.opensearch.security.rest.SecurityWhoAmIAction; import org.opensearch.security.rest.TenantInfoAction; +import org.opensearch.security.rest.resources.access.RestResourceAccessAction; import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; -import org.opensearch.security.rest.resources.access.list.RestListAccessibleResourcesAction; -import org.opensearch.security.rest.resources.access.revoke.RestRevokeResourceAccessAction; import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; -import org.opensearch.security.rest.resources.access.share.RestShareResourceAction; import org.opensearch.security.rest.resources.access.share.ShareResourceAction; -import org.opensearch.security.rest.resources.access.verify.RestVerifyResourceAccessAction; import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.impl.CType; @@ -700,14 +697,7 @@ public List getRestHandlers( ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT )) { - handlers.addAll( - List.of( - new RestShareResourceAction(), - new RestRevokeResourceAccessAction(), - new RestListAccessibleResourcesAction(), - new RestVerifyResourceAccessAction() - ) - ); + handlers.add(new RestResourceAccessAction()); } log.debug("Added {} rest handler(s)", handlers.size()); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java index 2e900169db..ba8a5cda5b 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java +++ b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java @@ -64,6 +64,8 @@ public class Utils { public final static String LEGACY_PLUGIN_API_ROUTE_PREFIX = LEGACY_PLUGIN_ROUTE_PREFIX + "/api"; + public final static String PLUGIN_RESOURCE_ROUTE_PREFIX = PLUGIN_ROUTE_PREFIX + "/resources"; + private static final ObjectMapper internalMapper = new ObjectMapper(); public static Map convertJsonToxToStructuredMap(ToXContent jsonContent) { diff --git a/src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java new file mode 100644 index 0000000000..787e92171c --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java @@ -0,0 +1,168 @@ +/* + * 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. + */ + +package org.opensearch.security.rest.resources.access; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.security.resources.RecipientType; +import org.opensearch.security.resources.RecipientTypeRegistry; +import org.opensearch.security.resources.ShareWith; +import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; +import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesRequest; +import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; +import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessRequest; +import org.opensearch.security.rest.resources.access.share.ShareResourceAction; +import org.opensearch.security.rest.resources.access.share.ShareResourceRequest; +import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; +import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessRequest; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.security.dlic.rest.api.Responses.badRequest; +import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_RESOURCE_ROUTE_PREFIX; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class RestResourceAccessAction extends BaseRestHandler { + + public RestResourceAccessAction() {} + + @Override + public List routes() { + return addRoutesPrefix( + ImmutableList.of( + new Route(GET, "/list/{resourceIndex}"), + new Route(POST, "/revoke"), + new Route(POST, "/share"), + new Route(POST, "/verify_access") + ), + PLUGIN_RESOURCE_ROUTE_PREFIX + ); + } + + @Override + public String getName() { + return "resource_access_action"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + consumeParams(request); // to avoid 400s + String path = request.path().split(PLUGIN_RESOURCE_ROUTE_PREFIX)[1].split("/")[1]; + return switch (path) { + case "list" -> channel -> handleListRequest(request, client, channel); + case "revoke" -> channel -> handleRevokeRequest(request, client, channel); + case "share" -> channel -> handleShareRequest(request, client, channel); + case "verify_access" -> channel -> handleVerifyRequest(request, client, channel); + default -> channel -> badRequest(channel, "Unknown route: " + path); + }; + } + + private void consumeParams(RestRequest request) { + request.param("resourceIndex", ""); + } + + public void handleListRequest(RestRequest request, NodeClient client, RestChannel channel) { + String resourceIndex = request.param("resourceIndex", ""); + final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(resourceIndex); + client.executeLocally( + ListAccessibleResourcesAction.INSTANCE, + listAccessibleResourcesRequest, + new RestToXContentListener<>(channel) + ); + + } + + public void handleRevokeRequest(RestRequest request, NodeClient client, RestChannel channel) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + String resourceIndex = (String) source.get("resource_index"); + @SuppressWarnings("unchecked") + Map> revokeSource = (Map>) source.get("entities"); + Map> revoke = revokeSource.entrySet() + .stream() + .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); + @SuppressWarnings("unchecked") + Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); + final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest( + resourceId, + resourceIndex, + revoke, + scopes + ); + client.executeLocally(RevokeResourceAccessAction.INSTANCE, revokeResourceAccessRequest, new RestToXContentListener<>(channel)); + } + + public void handleShareRequest(RestRequest request, NodeClient client, RestChannel channel) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + String resourceIndex = (String) source.get("resource_index"); + + ShareWith shareWith = parseShareWith(source); + final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, resourceIndex, shareWith); + client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); + } + + public void handleVerifyRequest(RestRequest request, NodeClient client, RestChannel channel) throws IOException { + Map source; + try (XContentParser parser = request.contentParser()) { + source = parser.map(); + } + + String resourceId = (String) source.get("resource_id"); + String resourceIndex = (String) source.get("resource_index"); + String scope = (String) source.get("scope"); + + final VerifyResourceAccessRequest verifyResourceAccessRequest = new VerifyResourceAccessRequest(resourceId, resourceIndex, scope); + client.executeLocally(VerifyResourceAccessAction.INSTANCE, verifyResourceAccessRequest, new RestToXContentListener<>(channel)); + } + + private ShareWith parseShareWith(Map source) throws IOException { + @SuppressWarnings("unchecked") + Map shareWithMap = (Map) source.get("share_with"); + if (shareWithMap == null || shareWithMap.isEmpty()) { + throw new IllegalArgumentException("share_with is required and cannot be empty"); + } + + String jsonString = XContentFactory.jsonBuilder().map(shareWithMap).toString(); + + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, jsonString) + ) { + return ShareWith.fromXContent(parser); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid share_with structure: " + e.getMessage(), e); + } + } +} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java deleted file mode 100644 index 85fb04554b..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/list/RestListAccessibleResourcesAction.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.list; - -import java.io.IOException; -import java.util.List; - -import com.google.common.collect.ImmutableList; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static org.opensearch.rest.RestRequest.Method.GET; -import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; -import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - -public class RestListAccessibleResourcesAction extends BaseRestHandler { - - public RestListAccessibleResourcesAction() {} - - @Override - public List routes() { - return addRoutesPrefix(ImmutableList.of(new Route(GET, "/resources/list/{resourceIndex}")), PLUGIN_ROUTE_PREFIX); - } - - @Override - public String getName() { - return "list_accessible_resources"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - String resourceIndex = request.param("resourceIndex", ""); - final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(resourceIndex); - return channel -> client.executeLocally( - ListAccessibleResourcesAction.INSTANCE, - listAccessibleResourcesRequest, - new RestToXContentListener<>(channel) - ); - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java deleted file mode 100644 index 2bde557884..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RestRevokeResourceAccessAction.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.revoke; - -import java.io.IOException; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import com.google.common.collect.ImmutableList; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.security.resources.RecipientType; -import org.opensearch.security.resources.RecipientTypeRegistry; - -import static org.opensearch.rest.RestRequest.Method.POST; -import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; -import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - -public class RestRevokeResourceAccessAction extends BaseRestHandler { - - public RestRevokeResourceAccessAction() {} - - @Override - public List routes() { - return addRoutesPrefix(ImmutableList.of(new Route(POST, "/resources/revoke")), PLUGIN_ROUTE_PREFIX); - } - - @Override - public String getName() { - return "revoke_resources_access"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceId = (String) source.get("resource_id"); - String resourceIndex = (String) source.get("resource_index"); - @SuppressWarnings("unchecked") - Map> revokeSource = (Map>) source.get("entities"); - Map> revoke = revokeSource.entrySet() - .stream() - .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); - @SuppressWarnings("unchecked") - Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); - final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest( - resourceId, - resourceIndex, - revoke, - scopes - ); - return channel -> client.executeLocally( - RevokeResourceAccessAction.INSTANCE, - revokeResourceAccessRequest, - new RestToXContentListener<>(channel) - ); - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java b/src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java deleted file mode 100644 index 3559ced3aa..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/share/RestShareResourceAction.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.share; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import com.google.common.collect.ImmutableList; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.common.xcontent.LoggingDeprecationHandler; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.core.xcontent.NamedXContentRegistry; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.security.resources.ShareWith; - -import static org.opensearch.rest.RestRequest.Method.POST; -import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; -import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - -public class RestShareResourceAction extends BaseRestHandler { - - public RestShareResourceAction() {} - - @Override - public List routes() { - return addRoutesPrefix(ImmutableList.of(new Route(POST, "/resources/share")), PLUGIN_ROUTE_PREFIX); - } - - @Override - public String getName() { - return "share_resources"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceId = (String) source.get("resource_id"); - String resourceIndex = (String) source.get("resource_index"); - - ShareWith shareWith = parseShareWith(source); - final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, resourceIndex, shareWith); - return channel -> client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); - } - - private ShareWith parseShareWith(Map source) throws IOException { - @SuppressWarnings("unchecked") - Map shareWithMap = (Map) source.get("share_with"); - if (shareWithMap == null || shareWithMap.isEmpty()) { - throw new IllegalArgumentException("share_with is required and cannot be empty"); - } - - String jsonString = XContentFactory.jsonBuilder().map(shareWithMap).toString(); - - try ( - XContentParser parser = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, jsonString) - ) { - return ShareWith.fromXContent(parser); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Invalid share_with structure: " + e.getMessage(), e); - } - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java deleted file mode 100644 index d8678c7c19..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/verify/RestVerifyResourceAccessAction.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.verify; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import com.google.common.collect.ImmutableList; - -import org.opensearch.client.node.NodeClient; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; - -import static org.opensearch.rest.RestRequest.Method.POST; -import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_ROUTE_PREFIX; -import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - -public class RestVerifyResourceAccessAction extends BaseRestHandler { - - public RestVerifyResourceAccessAction() {} - - @Override - public List routes() { - return addRoutesPrefix(ImmutableList.of(new Route(POST, "/resources/verify_access")), PLUGIN_ROUTE_PREFIX); - } - - @Override - public String getName() { - return "verify_resource_access"; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - Map source; - try (XContentParser parser = request.contentParser()) { - source = parser.map(); - } - - String resourceId = (String) source.get("resource_id"); - String resourceIndex = (String) source.get("resource_index"); - String scope = (String) source.get("scope"); - - final VerifyResourceAccessRequest verifyResourceAccessRequest = new VerifyResourceAccessRequest(resourceId, resourceIndex, scope); - return channel -> client.executeLocally( - VerifyResourceAccessAction.INSTANCE, - verifyResourceAccessRequest, - new RestToXContentListener<>(channel) - ); - } -} From a51563c3e6f335b075486d450fb704c131c68273 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 01:57:08 -0500 Subject: [PATCH 115/122] Simplifies rest actions for resource acess and updates sample plugin integration tests Signed-off-by: Darshit Chanpura --- .../sample/SampleResourcePluginTests.java | 34 ++--- .../security/OpenSearchSecurityPlugin.java | 27 +--- .../resources/ResourceAccessHandler.java | 12 +- ...cessAction.java => ResourceApiAction.java} | 125 +++++++++++------- .../list/ListAccessibleResourcesAction.java | 25 ---- .../list/ListAccessibleResourcesRequest.java | 51 ------- .../list/ListAccessibleResourcesResponse.java | 70 ---------- .../revoke/RevokeResourceAccessAction.java | 21 --- .../revoke/RevokeResourceAccessRequest.java | 79 ----------- .../revoke/RevokeResourceAccessResponse.java | 42 ------ .../access/share/ShareResourceAction.java | 25 ---- .../access/share/ShareResourceRequest.java | 61 --------- .../access/share/ShareResourceResponse.java | 42 ------ .../verify/VerifyResourceAccessAction.java | 25 ---- .../verify/VerifyResourceAccessRequest.java | 69 ---------- .../verify/VerifyResourceAccessResponse.java | 52 -------- ...ransportListAccessibleResourcesAction.java | 63 --------- .../TransportRevokeResourceAccessAction.java | 68 ---------- .../access/TransportShareResourceAction.java | 65 --------- .../TransportVerifyResourceAccessAction.java | 77 ----------- 20 files changed, 97 insertions(+), 936 deletions(-) rename src/main/java/org/opensearch/security/rest/resources/access/{RestResourceAccessAction.java => ResourceApiAction.java} (56%) delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java delete mode 100644 src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java delete mode 100644 src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java delete mode 100644 src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java delete mode 100644 src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java delete mode 100644 src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index f34cf0c561..beec8a8c10 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -17,8 +17,7 @@ import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.*; import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; import static org.opensearch.security.resources.ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; @@ -131,11 +130,11 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload(resourceId)); - response.assertStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); - assertThat(response.bodyAsJsonNode().toString(), containsString("User " + SHARED_WITH_USER.getName() + " is not authorized")); - // TODO these tests must check for unauthorized instead of internal-server-error - // response.assertStatusCode(HttpStatus.SC_UNAUTHORIZED); - // assertThat(response.bodyAsJsonNode().get("message").asText(), containsString("User is not authorized")); + response.assertStatusCode(HttpStatus.SC_FORBIDDEN); + assertThat( + response.bodyAsJsonNode().get("message").asText(), + containsString("User " + SHARED_WITH_USER.getName() + " is not authorized") + ); } // share resource with shared_with user @@ -144,7 +143,10 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { HttpResponse response = client.postJson(SECURITY_RESOURCE_SHARE_ENDPOINT, shareWithPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_OK); - assertThat(response.bodyAsJsonNode().get("message").asText(), containsString(resourceId)); + assertThat( + response.bodyAsJsonNode().get("share_with").get(SampleResourceScope.PUBLIC.value()).get("users").get(0).asText(), + containsString(SHARED_WITH_USER.getName()) + ); } // resource should now be visible to shared_with_user @@ -174,17 +176,17 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { + "\"}"; HttpResponse response = client.postJson(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); response.assertStatusCode(HttpStatus.SC_OK); - assertThat(response.getBody(), containsString("User has requested scope " + ResourceAccessScope.PUBLIC + " access")); + assertThat(response.bodyAsJsonNode().get("has_permission").asBoolean(), equalTo(true)); } // shared_with user should not be able to revoke access to admin's resource try (TestRestClient client = cluster.getRestClient(SHARED_WITH_USER)) { HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokeAccessPayload(resourceId)); - response.assertStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); - assertThat(response.bodyAsJsonNode().toString(), containsString("User " + SHARED_WITH_USER.getName() + " is not authorized")); - // TODO these tests must check for unauthorized instead of internal-server-error - // response.assertStatusCode(HttpStatus.SC_UNAUTHORIZED); - // assertThat(response.bodyAsJsonNode().get("message").asText(), containsString("User is not authorized")); + response.assertStatusCode(HttpStatus.SC_FORBIDDEN); + assertThat( + response.bodyAsJsonNode().get("message").asText(), + containsString("User " + SHARED_WITH_USER.getName() + " is not authorized") + ); } // revoke share_with_user's access @@ -192,7 +194,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { Thread.sleep(1000); HttpResponse response = client.postJson(SECURITY_RESOURCE_REVOKE_ENDPOINT, revokeAccessPayload(resourceId)); response.assertStatusCode(HttpStatus.SC_OK); - assertThat(response.bodyAsJsonNode().toString(), containsString("Resource " + resourceId + " access revoked successfully.")); + assertThat(response.bodyAsJsonNode().get("share_with"), nullValue()); } // verify access - share_with_user should no longer have access to admin's resource @@ -206,7 +208,7 @@ public void testCreateUpdateDeleteSampleResource() throws Exception { + "\"}"; HttpResponse response = client.postJson(SECURITY_RESOURCE_VERIFY_ENDPOINT, verifyAccessPayload); response.assertStatusCode(HttpStatus.SC_OK); - assertThat(response.getBody(), containsString("User does not have requested scope " + ResourceAccessScope.PUBLIC + " access")); + assertThat(response.bodyAsJsonNode().get("has_permission").asBoolean(), equalTo(false)); } // delete sample resource diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 5b3ebbd635..6b1f435256 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -190,11 +190,7 @@ import org.opensearch.security.rest.SecurityInfoAction; import org.opensearch.security.rest.SecurityWhoAmIAction; import org.opensearch.security.rest.TenantInfoAction; -import org.opensearch.security.rest.resources.access.RestResourceAccessAction; -import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; -import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; -import org.opensearch.security.rest.resources.access.share.ShareResourceAction; -import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; +import org.opensearch.security.rest.resources.access.ResourceApiAction; import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.setting.OpensearchDynamicSetting; @@ -220,10 +216,6 @@ import org.opensearch.security.transport.DefaultInterClusterRequestEvaluator; import org.opensearch.security.transport.InterClusterRequestEvaluator; import org.opensearch.security.transport.SecurityInterceptor; -import org.opensearch.security.transport.resources.access.TransportListAccessibleResourcesAction; -import org.opensearch.security.transport.resources.access.TransportRevokeResourceAccessAction; -import org.opensearch.security.transport.resources.access.TransportShareResourceAction; -import org.opensearch.security.transport.resources.access.TransportVerifyResourceAccessAction; import org.opensearch.security.user.User; import org.opensearch.security.user.UserService; import org.opensearch.tasks.Task; @@ -697,7 +689,7 @@ public List getRestHandlers( ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT )) { - handlers.add(new RestResourceAccessAction()); + handlers.add(new ResourceApiAction(resourceAccessHandler)); } log.debug("Added {} rest handler(s)", handlers.size()); } @@ -726,21 +718,6 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre actions.add(new ActionHandler<>(CertificatesActionType.INSTANCE, TransportCertificatesInfoNodesAction.class)); } actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class)); - - // Resource-access-control related actions - if (settings.getAsBoolean( - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, - ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT - )) { - actions.addAll( - List.of( - new ActionHandler<>(ShareResourceAction.INSTANCE, TransportShareResourceAction.class), - new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, TransportRevokeResourceAccessAction.class), - new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, TransportListAccessibleResourcesAction.class), - new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, TransportVerifyResourceAccessAction.class) - ) - ); - } } return actions; } diff --git a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java index 53ce446881..01deb71d66 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceAccessHandler.java @@ -105,17 +105,7 @@ public Future getAccessibleResourceIdsForCurrentUser(String resourceIndex, // 2. If the user is admin, simply fetch all resources if (adminDNs.isAdmin(user)) { - loadAllResources(resourceIndex, new ActionListener<>() { - @Override - public void onResponse(Set allResources) { - listener.onResponse(allResources); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }); + loadAllResources(resourceIndex, ActionListener.wrap(listener::onResponse, listener::onFailure)); return null; } diff --git a/src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java similarity index 56% rename from src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java rename to src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java index 787e92171c..07a29de897 100644 --- a/src/main/java/org/opensearch/security/rest/resources/access/RestResourceAccessAction.java +++ b/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java @@ -16,38 +16,38 @@ import java.util.stream.Collectors; import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.opensearch.client.node.NodeClient; import org.opensearch.common.xcontent.LoggingDeprecationHandler; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.security.resources.RecipientType; -import org.opensearch.security.resources.RecipientTypeRegistry; -import org.opensearch.security.resources.ShareWith; -import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; -import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesRequest; -import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; -import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessRequest; -import org.opensearch.security.rest.resources.access.share.ShareResourceAction; -import org.opensearch.security.rest.resources.access.share.ShareResourceRequest; -import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; -import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessRequest; +import org.opensearch.security.resources.*; +import org.opensearch.security.spi.resources.Resource; import static org.opensearch.rest.RestRequest.Method.GET; import static org.opensearch.rest.RestRequest.Method.POST; -import static org.opensearch.security.dlic.rest.api.Responses.badRequest; +import static org.opensearch.security.dlic.rest.api.Responses.*; import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_RESOURCE_ROUTE_PREFIX; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; -public class RestResourceAccessAction extends BaseRestHandler { +public class ResourceApiAction extends BaseRestHandler { + private static final Logger LOGGER = LogManager.getLogger(ResourceApiAction.class); - public RestResourceAccessAction() {} + private final ResourceAccessHandler resourceAccessHandler; + + public ResourceApiAction(ResourceAccessHandler resourceAccessHandler) { + this.resourceAccessHandler = resourceAccessHandler; + } @Override public List routes() { @@ -64,18 +64,18 @@ public List routes() { @Override public String getName() { - return "resource_access_action"; + return "resource_api_action"; } @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - consumeParams(request); // to avoid 400s + consumeParams(request); // early consume params to avoid 400s String path = request.path().split(PLUGIN_RESOURCE_ROUTE_PREFIX)[1].split("/")[1]; return switch (path) { - case "list" -> channel -> handleListRequest(request, client, channel); - case "revoke" -> channel -> handleRevokeRequest(request, client, channel); - case "share" -> channel -> handleShareRequest(request, client, channel); - case "verify_access" -> channel -> handleVerifyRequest(request, client, channel); + case "list" -> channel -> handleListResources(request, channel); + case "revoke" -> channel -> handleRevokeResource(request, channel); + case "share" -> channel -> handleShareResource(request, channel); + case "verify_access" -> channel -> handleVerifyRequest(request, channel); default -> channel -> badRequest(channel, "Unknown route: " + path); }; } @@ -84,42 +84,33 @@ private void consumeParams(RestRequest request) { request.param("resourceIndex", ""); } - public void handleListRequest(RestRequest request, NodeClient client, RestChannel channel) { + private void handleListResources(RestRequest request, RestChannel channel) { String resourceIndex = request.param("resourceIndex", ""); - final ListAccessibleResourcesRequest listAccessibleResourcesRequest = new ListAccessibleResourcesRequest(resourceIndex); - client.executeLocally( - ListAccessibleResourcesAction.INSTANCE, - listAccessibleResourcesRequest, - new RestToXContentListener<>(channel) + resourceAccessHandler.getAccessibleResourcesForCurrentUser( + resourceIndex, + ActionListener.wrap(resources -> sendResponse(channel, resources), e -> handleError(channel, e.getMessage(), e)) ); - } - public void handleRevokeRequest(RestRequest request, NodeClient client, RestChannel channel) throws IOException { + private void handleShareResource(RestRequest request, RestChannel channel) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); } - String resourceId = (String) source.get("resource_id"); String resourceIndex = (String) source.get("resource_index"); - @SuppressWarnings("unchecked") - Map> revokeSource = (Map>) source.get("entities"); - Map> revoke = revokeSource.entrySet() - .stream() - .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); - @SuppressWarnings("unchecked") - Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); - final RevokeResourceAccessRequest revokeResourceAccessRequest = new RevokeResourceAccessRequest( + + ShareWith shareWith = parseShareWith(source); + resourceAccessHandler.shareWith( resourceId, resourceIndex, - revoke, - scopes + shareWith, + ActionListener.wrap(response -> sendResponse(channel, response), e -> handleError(channel, e.getMessage(), e)) ); - client.executeLocally(RevokeResourceAccessAction.INSTANCE, revokeResourceAccessRequest, new RestToXContentListener<>(channel)); } - public void handleShareRequest(RestRequest request, NodeClient client, RestChannel channel) throws IOException { + @SuppressWarnings("unchecked") + private void handleRevokeResource(RestRequest request, RestChannel channel) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); @@ -128,12 +119,21 @@ public void handleShareRequest(RestRequest request, NodeClient client, RestChann String resourceId = (String) source.get("resource_id"); String resourceIndex = (String) source.get("resource_index"); - ShareWith shareWith = parseShareWith(source); - final ShareResourceRequest shareResourceRequest = new ShareResourceRequest(resourceId, resourceIndex, shareWith); - client.executeLocally(ShareResourceAction.INSTANCE, shareResourceRequest, new RestToXContentListener<>(channel)); + Map> revokeSource = (Map>) source.get("entities"); + Map> revoke = revokeSource.entrySet() + .stream() + .collect(Collectors.toMap(entry -> RecipientTypeRegistry.fromValue(entry.getKey()), Map.Entry::getValue)); + Set scopes = new HashSet<>(source.containsKey("scopes") ? (List) source.get("scopes") : List.of()); + resourceAccessHandler.revokeAccess( + resourceId, + resourceIndex, + revoke, + scopes, + ActionListener.wrap(response -> sendResponse(channel, response), e -> handleError(channel, e.getMessage(), e)) + ); } - public void handleVerifyRequest(RestRequest request, NodeClient client, RestChannel channel) throws IOException { + private void handleVerifyRequest(RestRequest request, RestChannel channel) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { source = parser.map(); @@ -143,12 +143,17 @@ public void handleVerifyRequest(RestRequest request, NodeClient client, RestChan String resourceIndex = (String) source.get("resource_index"); String scope = (String) source.get("scope"); - final VerifyResourceAccessRequest verifyResourceAccessRequest = new VerifyResourceAccessRequest(resourceId, resourceIndex, scope); - client.executeLocally(VerifyResourceAccessAction.INSTANCE, verifyResourceAccessRequest, new RestToXContentListener<>(channel)); + resourceAccessHandler.hasPermission( + resourceId, + resourceIndex, + scope, + ActionListener.wrap(response -> sendResponse(channel, response), e -> handleError(channel, e.getMessage(), e)) + ); } + @SuppressWarnings("unchecked") private ShareWith parseShareWith(Map source) throws IOException { - @SuppressWarnings("unchecked") + // Parse request body into ShareWith object Map shareWithMap = (Map) source.get("share_with"); if (shareWithMap == null || shareWithMap.isEmpty()) { throw new IllegalArgumentException("share_with is required and cannot be empty"); @@ -165,4 +170,26 @@ private ShareWith parseShareWith(Map source) throws IOException throw new IllegalArgumentException("Invalid share_with structure: " + e.getMessage(), e); } } + + @SuppressWarnings("unchecked") + private void sendResponse(RestChannel channel, Object response) throws IOException { + if (response instanceof Set) { + Set resources = (Set) response; + ok(channel, (builder, params) -> builder.startObject().field("resources", resources).endObject()); + } else if (response instanceof ResourceSharing resourceSharing) { + ok(channel, (resourceSharing::toXContent)); + } else if (response instanceof Boolean) { + ok(channel, (builder, params) -> builder.startObject().field("has_permission", String.valueOf(response)).endObject()); + } + } + + private void handleError(RestChannel channel, String message, Exception e) { + LOGGER.error(message, e); + if (message.contains("not authorized")) { + forbidden(channel, message); + } else if (message.contains("no authenticated")) { + unauthorized(channel); + } + channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, message)); + } } diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java deleted file mode 100644 index 3a8aa6ae59..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesAction.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.list; - -import org.opensearch.action.ActionType; - -/** - * Action to list resources - */ -public class ListAccessibleResourcesAction extends ActionType { - - public static final ListAccessibleResourcesAction INSTANCE = new ListAccessibleResourcesAction(); - - public static final String NAME = "cluster:admin/security/resources/list"; - - private ListAccessibleResourcesAction() { - super(NAME, ListAccessibleResourcesResponse::new); - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java deleted file mode 100644 index 414e25e305..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.list; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; - -/** - * Request object for ListSampleResource transport action - */ -public class ListAccessibleResourcesRequest extends ActionRequest { - - private final String resourceIndex; - - public ListAccessibleResourcesRequest(String resourceIndex) { - this.resourceIndex = resourceIndex; - } - - /** - * Constructor with stream input - * @param in the stream input - * @throws IOException IOException - */ - public ListAccessibleResourcesRequest(final StreamInput in) throws IOException { - this.resourceIndex = in.readString(); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(this.resourceIndex); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public String getResourceIndex() { - return resourceIndex; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java deleted file mode 100644 index 8bb1f0ea02..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/list/ListAccessibleResourcesResponse.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.list; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.Set; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.security.spi.resources.Resource; - -/** - * Response to a ListAccessibleResourcesRequest - */ -public class ListAccessibleResourcesResponse extends ActionResponse implements ToXContentObject { - private final Set resources; - private final String resourceClass; - - public ListAccessibleResourcesResponse(String resourceClass, Set resources) { - this.resourceClass = resourceClass; - this.resources = resources; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(resourceClass); - out.writeCollection(resources); - } - - public ListAccessibleResourcesResponse(StreamInput in) throws IOException { - this.resourceClass = in.readString(); - this.resources = readResourcesFromStream(in); - } - - private Set readResourcesFromStream(StreamInput in) { - try { - // TODO check if there is a better way to handle this - Class clazz = Class.forName(this.resourceClass); - @SuppressWarnings("unchecked") - Class resourceClass = (Class) clazz; - return in.readSet(i -> { - try { - return resourceClass.getDeclaredConstructor(StreamInput.class).newInstance(i); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException(e); - } - }); - } catch (ClassNotFoundException | IOException e) { - return Set.of(); - } - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("resources", resources); - builder.endObject(); - return builder; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java deleted file mode 100644 index e27ce05a2b..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessAction.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.revoke; - -import org.opensearch.action.ActionType; - -public class RevokeResourceAccessAction extends ActionType { - public static final RevokeResourceAccessAction INSTANCE = new RevokeResourceAccessAction(); - - public static final String NAME = "cluster:admin/security/resources/revoke"; - - private RevokeResourceAccessAction() { - super(NAME, RevokeResourceAccessResponse::new); - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java deleted file mode 100644 index 355658cf4c..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessRequest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.revoke; - -import java.io.IOException; -import java.util.Map; -import java.util.Set; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.security.resources.RecipientType; - -public class RevokeResourceAccessRequest extends ActionRequest { - - private final String resourceId; - private final String resourceIndex; - private final Map> revokeAccess; - private final Set scopes; - - public RevokeResourceAccessRequest( - String resourceId, - String resourceIndex, - Map> revokeAccess, - Set scopes - ) { - this.resourceId = resourceId; - this.resourceIndex = resourceIndex; - this.revokeAccess = revokeAccess; - this.scopes = scopes; - } - - public RevokeResourceAccessRequest(StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.resourceIndex = in.readString(); - this.revokeAccess = in.readMap(input -> new RecipientType(input.readString()), input -> input.readSet(StreamInput::readString)); - this.scopes = in.readSet(StreamInput::readString); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeString(resourceIndex); - out.writeMap( - revokeAccess, - (streamOutput, recipientType) -> streamOutput.writeString(recipientType.type()), - StreamOutput::writeStringCollection - ); - out.writeStringCollection(scopes); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public String getResourceId() { - return resourceId; - } - - public String getResourceIndex() { - return resourceIndex; - } - - public Map> getRevokeAccess() { - return revokeAccess; - } - - public Set getScopes() { - return scopes; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java deleted file mode 100644 index 090dfb54d0..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/revoke/RevokeResourceAccessResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.revoke; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class RevokeResourceAccessResponse extends ActionResponse implements ToXContentObject { - private final String message; - - public RevokeResourceAccessResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - public RevokeResourceAccessResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java deleted file mode 100644 index a112108bf1..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceAction.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.share; - -import org.opensearch.action.ActionType; - -/** - * Share resource - */ -public class ShareResourceAction extends ActionType { - - public static final ShareResourceAction INSTANCE = new ShareResourceAction(); - - public static final String NAME = "cluster:admin/security/resources/share"; - - private ShareResourceAction() { - super(NAME, ShareResourceResponse::new); - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java deleted file mode 100644 index 560e2967ba..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.share; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.security.resources.ShareWith; - -public class ShareResourceRequest extends ActionRequest { - - private final String resourceId; - private final String resourceIndex; - private final ShareWith shareWith; - - public ShareResourceRequest(String resourceId, String resourceIndex, ShareWith shareWith) { - this.resourceId = resourceId; - this.resourceIndex = resourceIndex; - this.shareWith = shareWith; - } - - public ShareResourceRequest(StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.resourceIndex = in.readString(); - this.shareWith = in.readNamedWriteable(ShareWith.class); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeString(resourceIndex); - out.writeNamedWriteable(shareWith); - } - - @Override - public ActionRequestValidationException validate() { - - return null; - } - - public String getResourceId() { - return resourceId; - } - - public String getResourceIndex() { - return resourceIndex; - } - - public ShareWith getShareWith() { - return shareWith; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java deleted file mode 100644 index 15b83c8d6f..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/share/ShareResourceResponse.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.share; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class ShareResourceResponse extends ActionResponse implements ToXContentObject { - private final String message; - - public ShareResourceResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - public ShareResourceResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java deleted file mode 100644 index 1f1f189ee1..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessAction.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.verify; - -import org.opensearch.action.ActionType; - -/** - * Action to verify resource access for current user - */ -public class VerifyResourceAccessAction extends ActionType { - - public static final VerifyResourceAccessAction INSTANCE = new VerifyResourceAccessAction(); - - public static final String NAME = "cluster:admin/security/resources/verify_access"; - - private VerifyResourceAccessAction() { - super(NAME, VerifyResourceAccessResponse::new); - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java deleted file mode 100644 index 529db51830..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessRequest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.verify; - -import java.io.IOException; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; - -public class VerifyResourceAccessRequest extends ActionRequest { - - private final String resourceId; - - private final String resourceIndex; - - private final String scope; - - /** - * Default constructor - */ - public VerifyResourceAccessRequest(String resourceId, String resourceIndex, String scope) { - this.resourceId = resourceId; - this.resourceIndex = resourceIndex; - this.scope = scope; - } - - /** - * Constructor with stream input - * @param in the stream input - * @throws IOException IOException - */ - public VerifyResourceAccessRequest(final StreamInput in) throws IOException { - this.resourceId = in.readString(); - this.resourceIndex = in.readString(); - this.scope = in.readString(); - } - - @Override - public void writeTo(final StreamOutput out) throws IOException { - out.writeString(resourceId); - out.writeString(resourceIndex); - out.writeString(scope); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public String getResourceId() { - return resourceId; - } - - public String getResourceIndex() { - return resourceIndex; - } - - public String getScope() { - return scope; - } -} diff --git a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java b/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java deleted file mode 100644 index a7fa7a2de4..0000000000 --- a/src/main/java/org/opensearch/security/rest/resources/access/verify/VerifyResourceAccessResponse.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.rest.resources.access.verify; - -import java.io.IOException; - -import org.opensearch.core.action.ActionResponse; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; - -public class VerifyResourceAccessResponse extends ActionResponse implements ToXContentObject { - private final String message; - - /** - * Default constructor - * - * @param message The message - */ - public VerifyResourceAccessResponse(String message) { - this.message = message; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); - } - - /** - * Constructor with StreamInput - * - * @param in the stream input - */ - public VerifyResourceAccessResponse(final StreamInput in) throws IOException { - message = in.readString(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field("message", message); - builder.endObject(); - return builder; - } -} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java deleted file mode 100644 index 25c727de67..0000000000 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportListAccessibleResourcesAction.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.transport.resources.access; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.security.OpenSearchSecurityPlugin; -import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesAction; -import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesRequest; -import org.opensearch.security.rest.resources.access.list.ListAccessibleResourcesResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -public class TransportListAccessibleResourcesAction extends HandledTransportAction< - ListAccessibleResourcesRequest, - ListAccessibleResourcesResponse> { - private static final Logger log = LogManager.getLogger(TransportListAccessibleResourcesAction.class); - private final ResourceAccessHandler resourceAccessHandler; - - @Inject - public TransportListAccessibleResourcesAction( - TransportService transportService, - ActionFilters actionFilters, - ResourceAccessHandler resourceAccessHandler - ) { - super(ListAccessibleResourcesAction.NAME, transportService, actionFilters, ListAccessibleResourcesRequest::new); - this.resourceAccessHandler = resourceAccessHandler; - } - - @Override - protected void doExecute(Task task, ListAccessibleResourcesRequest request, ActionListener listener) { - try { - resourceAccessHandler.getAccessibleResourcesForCurrentUser(request.getResourceIndex(), ActionListener.wrap(resources -> { - try { - log.info("Successfully fetched accessible resources for current user : {}", resources); - String resourceType = OpenSearchSecurityPlugin.getResourceProviders().get(request.getResourceIndex()).getResourceType(); - listener.onResponse(new ListAccessibleResourcesResponse(resourceType, resources)); - } catch (Exception e) { - log.error("Failed to process accessible resources response", e); - listener.onFailure(e); - } - }, e -> { - log.error("Failed to list accessible resources for current user", e); - listener.onFailure(e); - })); - } catch (Exception e) { - log.error("Failed to initiate accessible resources request", e); - listener.onFailure(e); - } - } -} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java deleted file mode 100644 index 97f139780d..0000000000 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportRevokeResourceAccessAction.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.transport.resources.access; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessAction; -import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessRequest; -import org.opensearch.security.rest.resources.access.revoke.RevokeResourceAccessResponse; -import org.opensearch.security.spi.resources.ResourceSharingException; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -public class TransportRevokeResourceAccessAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(TransportRevokeResourceAccessAction.class); - private final ResourceAccessHandler resourceAccessHandler; - - @Inject - public TransportRevokeResourceAccessAction( - TransportService transportService, - ActionFilters actionFilters, - ResourceAccessHandler resourceAccessHandler - ) { - super(RevokeResourceAccessAction.NAME, transportService, actionFilters, RevokeResourceAccessRequest::new); - this.resourceAccessHandler = resourceAccessHandler; - } - - @Override - protected void doExecute(Task task, RevokeResourceAccessRequest request, ActionListener listener) { - try { - this.resourceAccessHandler.revokeAccess( - request.getResourceId(), - request.getResourceIndex(), - request.getRevokeAccess(), - request.getScopes(), - ActionListener.wrap(resourceSharing -> { - if (resourceSharing == null) { - log.error("Failed to revoke access to resource {}", request.getResourceId()); - listener.onFailure(new ResourceSharingException("Failed to revoke access to resource " + request.getResourceId())); - } else { - log.info("Revoked resource access for resource: {} with {}", request.getResourceId(), resourceSharing.toString()); - listener.onResponse( - new RevokeResourceAccessResponse("Resource " + request.getResourceId() + " access revoked successfully.") - ); - } - }, e -> { - log.error("Exception while revoking access to resource {}: {}", request.getResourceId(), e.getMessage(), e); - listener.onFailure(e); - }) - ); - } catch (Exception e) { - listener.onFailure(e); - } - } - -} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java deleted file mode 100644 index 0de7987dc4..0000000000 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportShareResourceAction.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.transport.resources.access; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.OpenSearchException; -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.rest.resources.access.share.ShareResourceAction; -import org.opensearch.security.rest.resources.access.share.ShareResourceRequest; -import org.opensearch.security.rest.resources.access.share.ShareResourceResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -public class TransportShareResourceAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(TransportShareResourceAction.class); - private final ResourceAccessHandler resourceAccessHandler; - - @Inject - public TransportShareResourceAction( - TransportService transportService, - ActionFilters actionFilters, - ResourceAccessHandler resourceAccessHandler - ) { - super(ShareResourceAction.NAME, transportService, actionFilters, ShareResourceRequest::new); - this.resourceAccessHandler = resourceAccessHandler; - } - - @Override - protected void doExecute(Task task, ShareResourceRequest request, ActionListener listener) { - try { - this.resourceAccessHandler.shareWith( - request.getResourceId(), - request.getResourceIndex(), - request.getShareWith(), - ActionListener.wrap(resourceSharing -> { - if (resourceSharing == null) { - log.error("Failed to share resource {}", request.getResourceId()); - listener.onFailure(new OpenSearchException("Failed to share resource " + request.getResourceId())); - } else { - log.info("Shared resource : {} with {}", request.getResourceId(), resourceSharing.toString()); - listener.onResponse(new ShareResourceResponse("Resource " + request.getResourceId() + " shared successfully.")); - } - }, e -> { - log.error("Error while sharing resource {}: {}", request.getResourceId(), e.getMessage(), e); - listener.onFailure(e); - }) - ); - } catch (Exception e) { - log.error("Exception while trying to share resource {}: {}", request.getResourceId(), e.getMessage(), e); - listener.onFailure(e); - } - } -} diff --git a/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java b/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java deleted file mode 100644 index 93965f9f0b..0000000000 --- a/src/main/java/org/opensearch/security/transport/resources/access/TransportVerifyResourceAccessAction.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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. - */ - -package org.opensearch.security.transport.resources.access; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import org.opensearch.action.support.ActionFilters; -import org.opensearch.action.support.HandledTransportAction; -import org.opensearch.client.Client; -import org.opensearch.common.inject.Inject; -import org.opensearch.core.action.ActionListener; -import org.opensearch.security.resources.ResourceAccessHandler; -import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessAction; -import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessRequest; -import org.opensearch.security.rest.resources.access.verify.VerifyResourceAccessResponse; -import org.opensearch.tasks.Task; -import org.opensearch.transport.TransportService; - -public class TransportVerifyResourceAccessAction extends HandledTransportAction { - private static final Logger log = LogManager.getLogger(TransportVerifyResourceAccessAction.class); - private final ResourceAccessHandler resourceAccessHandler; - - @Inject - public TransportVerifyResourceAccessAction( - TransportService transportService, - ActionFilters actionFilters, - Client nodeClient, - ResourceAccessHandler resourceAccessHandler - ) { - super(VerifyResourceAccessAction.NAME, transportService, actionFilters, VerifyResourceAccessRequest::new); - this.resourceAccessHandler = resourceAccessHandler; - } - - @Override - protected void doExecute(Task task, VerifyResourceAccessRequest request, ActionListener listener) { - try { - resourceAccessHandler.hasPermission( - request.getResourceId(), - request.getResourceIndex(), - request.getScope(), - new ActionListener<>() { - @Override - public void onResponse(Boolean hasRequestedScopeAccess) { - StringBuilder sb = new StringBuilder(); - sb.append("User "); - sb.append(hasRequestedScopeAccess ? "has" : "does not have"); - sb.append(" requested scope "); - sb.append(request.getScope()); - sb.append(" access to "); - sb.append(request.getResourceId()); - - log.info(sb.toString()); - - listener.onResponse(new VerifyResourceAccessResponse(sb.toString())); - } - - @Override - public void onFailure(Exception e) { - log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); - listener.onFailure(e); - } - } - ); - } catch (Exception e) { - log.info("Failed to check user permissions for resource {}", request.getResourceId(), e); - listener.onFailure(e); - } - } - -} From 6e0a87bf8873e30b910c91ed76170172276f5647 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 02:01:42 -0500 Subject: [PATCH 116/122] Fixes checkStyle errors Signed-off-by: Darshit Chanpura --- .../opensearch/sample/SampleResourcePluginTests.java | 4 +++- .../actions/rest/create/CreateResourceRestAction.java | 1 + .../rest/resources/access/ResourceApiAction.java | 11 +++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index beec8a8c10..9922f5a9c0 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -17,7 +17,9 @@ import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME; import static org.opensearch.security.resources.ResourceSharingConstants.OPENSEARCH_RESOURCE_SHARING_INDEX; import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java index f1805e1820..1975298f3f 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/rest/create/CreateResourceRestAction.java @@ -57,6 +57,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli } } + @SuppressWarnings("unchecked") private RestChannelConsumer updateResource(Map source, String resourceId, NodeClient client) throws IOException { String name = (String) source.get("name"); String description = source.containsKey("description") ? (String) source.get("description") : null; diff --git a/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java b/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java index 07a29de897..eeddda964b 100644 --- a/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java +++ b/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java @@ -31,12 +31,19 @@ import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; -import org.opensearch.security.resources.*; +import org.opensearch.security.resources.RecipientType; +import org.opensearch.security.resources.RecipientTypeRegistry; +import org.opensearch.security.resources.ResourceAccessHandler; +import org.opensearch.security.resources.ResourceSharing; +import org.opensearch.security.resources.ShareWith; import org.opensearch.security.spi.resources.Resource; import static org.opensearch.rest.RestRequest.Method.GET; import static org.opensearch.rest.RestRequest.Method.POST; -import static org.opensearch.security.dlic.rest.api.Responses.*; +import static org.opensearch.security.dlic.rest.api.Responses.badRequest; +import static org.opensearch.security.dlic.rest.api.Responses.forbidden; +import static org.opensearch.security.dlic.rest.api.Responses.ok; +import static org.opensearch.security.dlic.rest.api.Responses.unauthorized; import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_RESOURCE_ROUTE_PREFIX; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; From a22313fd1e36906556075db9b393bb972e61100f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 02:12:05 -0500 Subject: [PATCH 117/122] Fixes artifact and integ-test workflow Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6300c70f89..6c1e05f6eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,18 +111,18 @@ jobs: - name: Checkout security uses: actions/checkout@v4 - - name: Run Integration Tests + - name: Publish SPI to Local Maven uses: gradle/gradle-build-action@v3 with: cache-disabled: true - arguments: | - integrationTest -Dbuild.snapshot=false + arguments: :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false - - name: Publish SPI to Local Maven + - name: Run Integration Tests uses: gradle/gradle-build-action@v3 with: cache-disabled: true - arguments: :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false + arguments: | + integrationTest -Dbuild.snapshot=false - name: Run SampleResourcePlugin Integration Tests uses: gradle/gradle-build-action@v3 @@ -158,6 +158,12 @@ jobs: - name: Checkout security uses: actions/checkout@v4 + - name: Publish SPI to Local Maven + uses: gradle/gradle-build-action@v3 + with: + cache-disabled: true + arguments: :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false + - name: Run Resource Tests uses: gradle/gradle-build-action@v3 with: @@ -284,6 +290,6 @@ jobs: test -s ./build/distributions/opensearch-security-$security_plugin_version.pom && \ test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version.pom - - name: List files in build directory on failure - if: failure() - run: ls -al ./build/distributions/ + - name: List files in build directory on failure + if: failure() + run: ls -al ./build/distributions/ From 194fec3ee1971ecfab642ff41c4befef1f6bf5a5 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 02:24:26 -0500 Subject: [PATCH 118/122] Adds comment to resource access rest class and changes file name Signed-off-by: Darshit Chanpura --- .../security/OpenSearchSecurityPlugin.java | 4 +- ...ion.java => ResourceAccessRestAction.java} | 61 ++++++++++++++++--- 2 files changed, 56 insertions(+), 9 deletions(-) rename src/main/java/org/opensearch/security/rest/resources/access/{ResourceApiAction.java => ResourceAccessRestAction.java} (80%) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 6b1f435256..30691d47fd 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -190,7 +190,7 @@ import org.opensearch.security.rest.SecurityInfoAction; import org.opensearch.security.rest.SecurityWhoAmIAction; import org.opensearch.security.rest.TenantInfoAction; -import org.opensearch.security.rest.resources.access.ResourceApiAction; +import org.opensearch.security.rest.resources.access.ResourceAccessRestAction; import org.opensearch.security.securityconf.DynamicConfigFactory; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.setting.OpensearchDynamicSetting; @@ -689,7 +689,7 @@ public List getRestHandlers( ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED, ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT )) { - handlers.add(new ResourceApiAction(resourceAccessHandler)); + handlers.add(new ResourceAccessRestAction(resourceAccessHandler)); } log.debug("Added {} rest handler(s)", handlers.size()); } diff --git a/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java b/src/main/java/org/opensearch/security/rest/resources/access/ResourceAccessRestAction.java similarity index 80% rename from src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java rename to src/main/java/org/opensearch/security/rest/resources/access/ResourceAccessRestAction.java index eeddda964b..c4b26ba949 100644 --- a/src/main/java/org/opensearch/security/rest/resources/access/ResourceApiAction.java +++ b/src/main/java/org/opensearch/security/rest/resources/access/ResourceAccessRestAction.java @@ -47,12 +47,15 @@ import static org.opensearch.security.dlic.rest.support.Utils.PLUGIN_RESOURCE_ROUTE_PREFIX; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; -public class ResourceApiAction extends BaseRestHandler { - private static final Logger LOGGER = LogManager.getLogger(ResourceApiAction.class); +/** + * This class handles the REST API for resource access management. + */ +public class ResourceAccessRestAction extends BaseRestHandler { + private static final Logger LOGGER = LogManager.getLogger(ResourceAccessRestAction.class); private final ResourceAccessHandler resourceAccessHandler; - public ResourceApiAction(ResourceAccessHandler resourceAccessHandler) { + public ResourceAccessRestAction(ResourceAccessHandler resourceAccessHandler) { this.resourceAccessHandler = resourceAccessHandler; } @@ -87,10 +90,19 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli }; } + /** + * Consume params early to avoid 400s. + * @param request from which the params must be consumed + */ private void consumeParams(RestRequest request) { request.param("resourceIndex", ""); } + /** + * Handle the list resources request. + * @param request the request to handle + * @param channel the channel to send the response to + */ private void handleListResources(RestRequest request, RestChannel channel) { String resourceIndex = request.param("resourceIndex", ""); resourceAccessHandler.getAccessibleResourcesForCurrentUser( @@ -99,6 +111,12 @@ private void handleListResources(RestRequest request, RestChannel channel) { ); } + /** + * Handle the share resource request. + * @param request the request to handle + * @param channel the channel to send the response to + * @throws IOException if an I/O error occurs + */ private void handleShareResource(RestRequest request, RestChannel channel) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { @@ -116,6 +134,12 @@ private void handleShareResource(RestRequest request, RestChannel channel) throw ); } + /** + * Handle the revoke resource request. + * @param request the request to handle + * @param channel the channel to send the response to + * @throws IOException if an I/O error occurs + */ @SuppressWarnings("unchecked") private void handleRevokeResource(RestRequest request, RestChannel channel) throws IOException { Map source; @@ -140,6 +164,12 @@ private void handleRevokeResource(RestRequest request, RestChannel channel) thro ); } + /** + * Handle the verify request. + * @param request the request to handle + * @param channel the channel to send the response to + * @throws IOException if an I/O error occurs + */ private void handleVerifyRequest(RestRequest request, RestChannel channel) throws IOException { Map source; try (XContentParser parser = request.contentParser()) { @@ -158,9 +188,14 @@ private void handleVerifyRequest(RestRequest request, RestChannel channel) throw ); } + /** + * Parse the share with structure from the request body. + * @param source the request body + * @return the parsed ShareWith object + * @throws IOException if an I/O error occurs + */ @SuppressWarnings("unchecked") private ShareWith parseShareWith(Map source) throws IOException { - // Parse request body into ShareWith object Map shareWithMap = (Map) source.get("share_with"); if (shareWithMap == null || shareWithMap.isEmpty()) { throw new IllegalArgumentException("share_with is required and cannot be empty"); @@ -178,18 +213,30 @@ private ShareWith parseShareWith(Map source) throws IOException } } + /** + * Send the appropriate response to the channel. + * @param channel the channel to send the response to + * @param response the response to send + * @throws IOException if an I/O error occurs + */ @SuppressWarnings("unchecked") private void sendResponse(RestChannel channel, Object response) throws IOException { - if (response instanceof Set) { + if (response instanceof Set) { // list Set resources = (Set) response; ok(channel, (builder, params) -> builder.startObject().field("resources", resources).endObject()); - } else if (response instanceof ResourceSharing resourceSharing) { + } else if (response instanceof ResourceSharing resourceSharing) { // share & revoke ok(channel, (resourceSharing::toXContent)); - } else if (response instanceof Boolean) { + } else if (response instanceof Boolean) { // verify_access ok(channel, (builder, params) -> builder.startObject().field("has_permission", String.valueOf(response)).endObject()); } } + /** + * Handle errors that occur during request processing. + * @param channel the channel to send the error response to + * @param message the error message + * @param e the exception that caused the error + */ private void handleError(RestChannel channel, String message, Exception e) { LOGGER.error(message, e); if (message.contains("not authorized")) { From bf81c46b6652f294e2fe6c37a886875c82e92910 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 02:36:55 -0500 Subject: [PATCH 119/122] Adds : to avoid sample-resource-plugin tests from being triggered unnecessarily Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c1e05f6eb..9cbb5b42fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,7 @@ jobs: with: cache-disabled: true arguments: | - integrationTest -Dbuild.snapshot=false + :integrationTest -Dbuild.snapshot=false - name: Run SampleResourcePlugin Integration Tests uses: gradle/gradle-build-action@v3 @@ -169,7 +169,7 @@ jobs: with: cache-disabled: true arguments: | - integrationTest -Dbuild.snapshot=false --tests org.opensearch.security.ResourceFocusedTests + :integrationTest -Dbuild.snapshot=false --tests org.opensearch.security.ResourceFocusedTests backward-compatibility-build: runs-on: ubuntu-latest @@ -249,12 +249,6 @@ jobs: security_plugin_version_only_number=$(echo $security_plugin_version_no_snapshot | cut -d- -f1) test_qualifier=alpha2 - # Export variables to GitHub Environment - echo "SECURITY_PLUGIN_VERSION=$security_plugin_version" >> $GITHUB_ENV - echo "SECURITY_PLUGIN_VERSION_NO_SNAPSHOT=$security_plugin_version_no_snapshot" >> $GITHUB_ENV - echo "SECURITY_PLUGIN_VERSION_ONLY_NUMBER=$security_plugin_version_only_number" >> $GITHUB_ENV - echo "TEST_QUALIFIER=$test_qualifier" >> $GITHUB_ENV - # Debug print versions echo "Versions:" echo $security_plugin_version From 6d5c80fdf847985061a60ed984413f3b9fa791a9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 18:51:35 -0500 Subject: [PATCH 120/122] Fixes build-artifacts ci Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cbb5b42fb..c8e5202c29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -257,10 +257,10 @@ jobs: echo $test_qualifier # Publish SPI - ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal - ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false - ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier - ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=$test_qualifier + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version.jar + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_no_snapshot.jar + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier.jar + ./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT.jar # Build artifacts ./gradlew clean assemble && \ @@ -281,8 +281,7 @@ jobs: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && \ test -s ./build/distributions/opensearch-security-$security_plugin_version.zip && \ - test -s ./build/distributions/opensearch-security-$security_plugin_version.pom && \ - test -s ./sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-$security_plugin_version.pom + test -s ./build/distributions/opensearch-security-$security_plugin_version.pom - name: List files in build directory on failure if: failure() From ff56d24e5570beb0fbf42b5727346601f4e82484 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 19:52:44 -0500 Subject: [PATCH 121/122] Updates sample plugin readme Signed-off-by: Darshit Chanpura --- sample-resource-plugin/README.md | 102 +++++-------------------------- 1 file changed, 14 insertions(+), 88 deletions(-) diff --git a/sample-resource-plugin/README.md b/sample-resource-plugin/README.md index ccd73db983..f568544df2 100644 --- a/sample-resource-plugin/README.md +++ b/sample-resource-plugin/README.md @@ -1,14 +1,10 @@ # Resource Sharing and Access Control Plugin -This plugin demonstrates resource sharing and access control functionality, providing APIs to create, manage, and verify access to resources. The plugin enables fine-grained permissions for sharing and accessing resources, making it suitable for systems requiring robust security and collaboration. +This plugin demonstrates resource sharing and access control functionality, providing sample resource APIs and marking it as a resource sharing plugin via resource-sharing-spi. The access control is implemented on Security plugin and will be performed under the hood. ## Features -- Create and delete resources. -- Share resources with specific users, roles and/or backend_roles with specific scope(s). -- Revoke access to shared resources for a list of or all scopes. -- Verify access permissions for a given user within a given scope. -- List all resources accessible to current user. +- Create, update and delete resources. ## API Endpoints @@ -16,7 +12,7 @@ The plugin exposes the following six API endpoints: ### 1. Create Resource - **Endpoint:** `POST /_plugins/sample_resource_sharing/create` -- **Description:** Creates a new resource. Also creates a resource sharing entry if security plugin is enabled. +- **Description:** Creates a new resource. Behind the scenes a resource sharing entry will be created if security plugin is installed and feature is enabled. - **Request Body:** ```json { @@ -29,99 +25,29 @@ The plugin exposes the following six API endpoints: "message": "Resource created successfully." } ``` - -### 2. Delete Resource -- **Endpoint:** `DELETE /_plugins/sample_resource_sharing/{resource_id}` -- **Description:** Deletes a specified resource owned by the requesting user. -- **Response:** +### 2. Update Resource +- **Endpoint:** `POST /_plugins/sample_resource_sharing/update/{resourceId}` +- **Description:** Updates a resource. +- **Request Body:** ```json { - "message": "Resource deleted successfully." + "name": "" } ``` - -### 3. Share Resource -- **Endpoint:** `POST /_plugins/sample_resource_sharing/share` -- **Description:** Shares a resource with specified users or roles with defined scope. -- **Request Body:** - ```json - { - "resource_id" : "{{ADMIN_RESOURCE_ID}}", - "share_with" : { - "SAMPLE_FULL_ACCESS": { - "users": ["test"], - "roles": ["test_role"], - "backend_roles": ["test_backend_role"] - }, - "READ_ONLY": { - "users": ["test"], - "roles": ["test_role"], - "backend_roles": ["test_backend_role"] - }, - "READ_WRITE": { - "users": ["test"], - "roles": ["test_role"], - "backend_roles": ["test_backend_role"] - } - } - } - ``` -- **Response:** - ```json - { - "message": "Resource shared successfully." - } - ``` - -### 4. Revoke Access -- **Endpoint:** `POST /_plugins/sample_resource_sharing/revoke` -- **Description:** Revokes access to a resource for specified users or roles. -- **Request Body:** - ```json - { - "resource_id" : "", - "entities" : { - "users": ["test", "admin"], - "roles": ["test_role", "all_access"], - "backend_roles": ["test_backend_role", "admin"] - }, - "scopes": ["SAMPLE_FULL_ACCESS", "READ_ONLY", "READ_WRITE"] - } - ``` -- **Response:** - ```json - { - "message": "Resource access revoked successfully." - } - ``` - -### 5. Verify Access -- **Endpoint:** `GET /_plugins/sample_resource_sharing/verify_resource_access` -- **Description:** Verifies if a user or role has access to a specific resource with a specific scope. -- **Request Body:** - ```json - { - "resource_id": "", - "scope": "SAMPLE_FULL_ACCESS" - } - ``` - **Response:** ```json { - "message": "User has requested scope SAMPLE_FULL_ACCESS access to " + "message": "Resource updated successfully." } ``` -### 6. List Accessible Resources -- **Endpoint:** `GET /_plugins/sample_resource_sharing/list` -- **Description:** Lists all resources accessible to the requesting user or role. +### 3. Delete Resource +- **Endpoint:** `DELETE /_plugins/sample_resource_sharing/delete/{resource_id}` +- **Description:** Deletes a specified resource owned by the requesting user. - **Response:** ```json { - "resource-ids": [ - "", - "" - ] + "message": "Resource deleted successfully." } ``` @@ -140,7 +66,7 @@ The plugin exposes the following six API endpoints: 3. Build and deploy the plugin: ```bash $ ./gradlew clean build -x test -x integrationTest -x spotbugsIntegrationTest - $ ./bin/opensearch-plugin install file: /sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-3.0.0.0-SNAPSHOT.zip + $ ./bin/opensearch-plugin install file: /sample-resource-plugin/build/distributions/opensearch-sample-resource-plugin-.zip ``` ## License From c48a02b8dcf02dd8ed01c63acaac6c063bc0d33e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 19 Jan 2025 22:19:16 -0500 Subject: [PATCH 122/122] Removes lingering putPersistent calls Signed-off-by: Darshit Chanpura --- .../java/org/opensearch/security/filter/SecurityFilter.java | 1 - .../security/resources/ResourceSharingIndexListener.java | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index df165daecb..6b23fb6b53 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -339,7 +339,6 @@ private void ap log.info("Transport auth in passive mode and no user found. Injecting default user"); user = User.DEFAULT_TRANSPORT_USER; threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); - threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER, user); } else { log.error( "No user found for " diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java index 9b6f7f1832..d32a49c80e 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexListener.java @@ -19,6 +19,7 @@ import org.opensearch.index.engine.Engine; import org.opensearch.index.shard.IndexingOperationListener; import org.opensearch.security.auditlog.AuditLog; +import org.opensearch.security.auth.UserSubjectImpl; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; @@ -87,8 +88,9 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re String resourceId = index.id(); - User user = (User) threadPool.getThreadContext().getPersistent(ConfigConstants.OPENDISTRO_SECURITY_USER); - + final UserSubjectImpl userSubject = (UserSubjectImpl) threadPool.getThreadContext() + .getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER); + final User user = userSubject.getUser(); try { ResourceSharing sharing = this.resourceSharingIndexHandler.indexResourceSharing( resourceId,