From f25ac342398090f4b4cbb796fecb91596ab545e5 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 26 Aug 2025 15:54:45 -0400 Subject: [PATCH 1/9] WIP on tracking principals on the resource doc Signed-off-by: Craig Perkins --- .../resources/ResourceIndexListener.java | 11 ++++- .../ResourceSharingIndexHandler.java | 48 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java index e861394a86..9922308cfe 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java @@ -9,6 +9,7 @@ package org.opensearch.security.resources; import java.io.IOException; +import java.util.List; import java.util.Objects; import org.apache.logging.log4j.LogManager; @@ -54,7 +55,7 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re String resourceId = index.id(); // Only proceed if this was a create operation and for primary shard - if (!result.isCreated() && index.origin().equals(Engine.Operation.Origin.PRIMARY)) { + if (!result.isCreated() || !index.origin().equals(Engine.Operation.Origin.PRIMARY)) { log.debug("Skipping resource sharing entry creation as this was an update operation for resource {}", resourceId); return; } @@ -72,6 +73,14 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re resourceId, resourceIndex ); + this.resourceSharingIndexHandler.updateResourceVisibility( + resourceId, + resourceIndex, + List.of("user:" + user.getName()), + ActionListener.wrap((updateResponse) -> { + log.debug("postUpdate: Successfully updated visibility for resource {} within index {}", resourceId, resourceIndex); + }, (e) -> { log.debug(e.getMessage()); }) + ); }, e -> { log.debug(e.getMessage()); }); this.resourceSharingIndexHandler.indexResourceSharing(resourceId, resourceIndex, new CreatedBy(user.getName()), null, listener); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 96e89cae7b..c9c6720d69 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -35,6 +36,8 @@ import org.opensearch.action.search.SearchResponse; import org.opensearch.action.search.SearchScrollRequest; import org.opensearch.action.support.WriteRequest; +import org.opensearch.action.update.UpdateRequest; +import org.opensearch.action.update.UpdateResponse; import org.opensearch.common.inject.Inject; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; @@ -135,6 +138,51 @@ public static String getSharingIndex(String resourceIndex) { return resourceIndex + "-sharing"; } + /** + * Updates the visibility of a resource document by replacing its {@code principals} field + * with the provided list of principals. The update is executed immediately with + * {@link WriteRequest.RefreshPolicy#IMMEDIATE} to ensure the change is visible in subsequent + * searches. + *

+ * The supplied {@link ActionListener} will be invoked with the {@link UpdateResponse} + * on success, or with an exception on failure. + * + * @param resourceId the unique identifier of the resource document to update + * @param resourceIndex the name of the index containing the resource + * @param principals the list of principals (e.g. {@code user:alice}, {@code role:admin}) + * that should be assigned to the resource + * @param listener callback that will be notified with the update response or an error + */ + public void updateResourceVisibility( + String resourceId, + String resourceIndex, + List principals, + ActionListener listener + ) { + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { + UpdateRequest ur = client.prepareUpdate(resourceIndex, resourceId) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setDoc(Map.of("principals", principals)) + .setId(resourceId) + .request(); + + ActionListener urListener = ActionListener.wrap(response -> { + ctx.restore(); + LOGGER.info( + "Successfully updated visibility of resource {} in index {} to principals {}.", + resourceIndex, + resourceId, + principals + ); + listener.onResponse(response); + }, (e) -> { + LOGGER.error("Failed to update visibility in [{}] for resource [{}]", resourceIndex, resourceId, e); + listener.onFailure(e); + }); + client.update(ur, urListener); + } + } + /** * Creates or updates a resource sharing record in the dedicated resource sharing index. * This method handles the persistence of sharing metadata for resources, including From 746b239fe78509e848d510f619c1feb0429c8831 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 26 Aug 2025 21:16:31 -0400 Subject: [PATCH 2/9] Update resource visibility in indexResourceSharing Signed-off-by: Craig Perkins --- .../resources/ResourceIndexListener.java | 9 --------- .../resources/ResourceSharingIndexHandler.java | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java b/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java index 9922308cfe..765320eec2 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java +++ b/src/main/java/org/opensearch/security/resources/ResourceIndexListener.java @@ -9,7 +9,6 @@ package org.opensearch.security.resources; import java.io.IOException; -import java.util.List; import java.util.Objects; import org.apache.logging.log4j.LogManager; @@ -73,14 +72,6 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re resourceId, resourceIndex ); - this.resourceSharingIndexHandler.updateResourceVisibility( - resourceId, - resourceIndex, - List.of("user:" + user.getName()), - ActionListener.wrap((updateResponse) -> { - log.debug("postUpdate: Successfully updated visibility for resource {} within index {}", resourceId, resourceIndex); - }, (e) -> { log.debug(e.getMessage()); }) - ); }, e -> { log.debug(e.getMessage()); }); this.resourceSharingIndexHandler.indexResourceSharing(resourceId, resourceIndex, new CreatedBy(user.getName()), null, listener); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index c9c6720d69..fa889a650f 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -225,7 +225,22 @@ public void indexResourceSharing( ActionListener irListener = ActionListener.wrap(idxResponse -> { ctx.restore(); LOGGER.info("Successfully created {} entry for resource {} in index {}.", resourceSharingIndex, resourceId, resourceIndex); - listener.onResponse(entry); + updateResourceVisibility( + resourceId, + resourceIndex, + List.of("user:" + createdBy.getUsername()), + ActionListener.wrap((updateResponse) -> { + LOGGER.debug( + "postUpdate: Successfully updated visibility for resource {} within index {}", + resourceId, + resourceIndex + ); + listener.onResponse(entry); + }, (e) -> { + LOGGER.error("Failed to create principals field in [{}] for resource [{}]", resourceIndex, resourceId, e); + listener.onResponse(entry); + }) + ); }, (e) -> { if (ExceptionsHelper.unwrapCause(e) instanceof VersionConflictEngineException) { // already exists → skipping From 3b1f0fd60044d5a71db114239f0a2508184d13f4 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 26 Aug 2025 21:33:51 -0400 Subject: [PATCH 3/9] Update visibility when sharing Signed-off-by: Craig Perkins --- .../resources/sharing/ResourceSharing.java | 45 +++++++++++++++++++ .../ResourceSharingIndexHandler.java | 13 +++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java index 30cfb146d4..a7c25241b1 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java @@ -9,8 +9,10 @@ package org.opensearch.security.spi.resources.sharing; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -267,4 +269,47 @@ public Set fetchAccessLevels(Recipient recipientType, Set entiti } return matchingGroups; } + + /** + * Returns all principals (users, roles, backend_roles) that have access to this resource, + * including the creator and all shared recipients, formatted with appropriate prefixes. + * + * @return List of principals in format ["user:username", "role:rolename", "backend:backend_role"] + */ + public List getAllPrincipals() { + List principals = new ArrayList<>(); + + // Add creator + if (createdBy != null) { + principals.add("user:" + createdBy.getUsername()); + } + + // Add shared recipients + if (shareWith != null) { + // shared with at any access level + for (Recipients recipients : shareWith.getSharingInfo().values()) { + Map> recipientMap = recipients.getRecipients(); + + // Add users + Set users = recipientMap.getOrDefault(Recipient.USERS, Collections.emptySet()); + for (String user : users) { + principals.add("user:" + user); + } + + // Add roles + Set roles = recipientMap.getOrDefault(Recipient.ROLES, Collections.emptySet()); + for (String role : roles) { + principals.add("role:" + role); + } + + // Add backend roles + Set backendRoles = recipientMap.getOrDefault(Recipient.BACKEND_ROLES, Collections.emptySet()); + for (String backendRole : backendRoles) { + principals.add("backend:" + backendRole); + } + } + } + + return principals; + } } diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index fa889a650f..828182e13f 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -534,7 +534,18 @@ public void share(String resourceId, String resourceIndex, ShareWith shareWith, resourceId, resourceIndex ); - listener.onResponse(sharingInfo); + updateResourceVisibility( + resourceId, + resourceIndex, + sharingInfo.getAllPrincipals(), + ActionListener.wrap((updateResponse) -> { + LOGGER.debug("Successfully updated visibility for resource {} within index {}", resourceId, resourceIndex); + listener.onResponse(sharingInfo); + }, (e) -> { + LOGGER.error("Failed to update principals field in [{}] for resource [{}]", resourceIndex, resourceId, e); + listener.onResponse(sharingInfo); + }) + ); }, (failResponse) -> { LOGGER.error(failResponse.getMessage()); listener.onFailure(failResponse); From 0bfc778df03ac0e5dcea82ad5253221d07dc76cc Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 26 Aug 2025 21:35:12 -0400 Subject: [PATCH 4/9] Update in revoke as well Signed-off-by: Craig Perkins --- .../resources/ResourceSharingIndexHandler.java | 13 ++++++++++++- 1 file changed, 12 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 828182e13f..0bff18016c 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -634,7 +634,18 @@ public void revoke(String resourceId, String resourceIndex, ShareWith revokeAcce ActionListener irListener = ActionListener.wrap(idxResponse -> { ctx.restore(); LOGGER.info("Successfully revoked access of {} to resource {} in index {}.", revokeAccess, resourceId, resourceIndex); - listener.onResponse(sharingInfo); + updateResourceVisibility( + resourceId, + resourceIndex, + sharingInfo.getAllPrincipals(), + ActionListener.wrap((updateResponse) -> { + LOGGER.debug("Successfully updated visibility for resource {} within index {}", resourceId, resourceIndex); + listener.onResponse(sharingInfo); + }, (e) -> { + LOGGER.error("Failed to update principals field in [{}] for resource [{}]", resourceIndex, resourceId, e); + listener.onResponse(sharingInfo); + }) + ); }, (failResponse) -> { LOGGER.error(failResponse.getMessage()); listener.onFailure(failResponse); From 4aa049bf318a96b236919538a7a67ea4386978b6 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 27 Aug 2025 15:43:19 -0400 Subject: [PATCH 5/9] Keep track of list of principals for which sharable resource is visible for searching Signed-off-by: Craig Perkins --- .../sample/resource/MigrateApiTests.java | 2 - .../resource/SecurityDisabledTests.java | 2 - .../opensearch/sample/resource/TestUtils.java | 2 - .../sample/secure/SecurePluginTests.java | 2 - .../SearchResourceTransportAction.java | 1 + .../ResourceSharingIndexHandler.java | 149 ++++++++++-------- 6 files changed, 82 insertions(+), 76 deletions(-) diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/MigrateApiTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/MigrateApiTests.java index f9b9780999..fb6f92276b 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/MigrateApiTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/MigrateApiTests.java @@ -26,7 +26,6 @@ import org.junit.runner.RunWith; import org.opensearch.Version; -import org.opensearch.painless.PainlessModulePlugin; import org.opensearch.plugins.PluginInfo; import org.opensearch.sample.SampleResourcePlugin; import org.opensearch.security.OpenSearchSecurityPlugin; @@ -64,7 +63,6 @@ public class MigrateApiTests { @ClassRule public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.DEFAULT) - .plugin(PainlessModulePlugin.class) .plugin( new PluginInfo( SampleResourcePlugin.class.getName(), diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/SecurityDisabledTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/SecurityDisabledTests.java index 2b7aa6c4b4..3c9379d012 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/SecurityDisabledTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/SecurityDisabledTests.java @@ -19,7 +19,6 @@ import org.junit.runner.RunWith; import org.opensearch.Version; -import org.opensearch.painless.PainlessModulePlugin; import org.opensearch.plugins.PluginInfo; import org.opensearch.sample.SampleResourcePlugin; import org.opensearch.security.OpenSearchSecurityPlugin; @@ -65,7 +64,6 @@ public class SecurityDisabledTests { false ) ) - .plugin(PainlessModulePlugin.class) .loadConfigurationIntoIndex(false) .nodeSettings(Map.of("plugins.security.disabled", true, "plugins.security.ssl.http.enabled", false)) .build(); diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/TestUtils.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/TestUtils.java index cd6977e711..36126aa463 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/TestUtils.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/resource/TestUtils.java @@ -22,7 +22,6 @@ import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.painless.PainlessModulePlugin; import org.opensearch.plugins.PluginInfo; import org.opensearch.sample.SampleResourcePlugin; import org.opensearch.security.OpenSearchSecurityPlugin; @@ -112,7 +111,6 @@ public static LocalCluster newCluster(boolean featureEnabled, boolean systemInde false ) ) - .plugin(PainlessModulePlugin.class) .anonymousAuth(true) .authc(AUTHC_HTTPBASIC_INTERNAL) .users(USER_ADMIN, FULL_ACCESS_USER, LIMITED_ACCESS_USER, NO_ACCESS_USER) diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/secure/SecurePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/secure/SecurePluginTests.java index ff88739d5c..ae33cada04 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/secure/SecurePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/secure/SecurePluginTests.java @@ -19,7 +19,6 @@ import org.opensearch.Version; import org.opensearch.core.rest.RestStatus; -import org.opensearch.painless.PainlessModulePlugin; import org.opensearch.plugins.PluginInfo; import org.opensearch.sample.SampleResourcePlugin; import org.opensearch.security.OpenSearchSecurityPlugin; @@ -47,7 +46,6 @@ public class SecurePluginTests { .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(PainlessModulePlugin.class) .plugin( new PluginInfo( SampleResourcePlugin.class.getName(), diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java index 0add62ba8d..f29ef3e221 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java @@ -70,6 +70,7 @@ protected void doExecute(Task task, SearchRequest request, ActionListener listener) { SearchSourceBuilder src = request.source() != null ? request.source() : new SearchSourceBuilder(); ActionListener> idsListener = ActionListener.wrap(resourceIds -> { + System.out.println("resourceIds to filter: " + resourceIds); mergeAccessibleFilter(src, resourceIds); request.source(src); nodeClient.search(request, listener); diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 0bff18016c..3ec62f0e8a 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -53,8 +53,6 @@ import org.opensearch.index.query.BoolQueryBuilder; import org.opensearch.index.query.MatchAllQueryBuilder; import org.opensearch.index.query.QueryBuilders; -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; @@ -339,28 +337,100 @@ public void fetchAccessibleResourceIds( ActionListener> listener ) { final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); - String resourceSharingIndex = getSharingIndex(resourceIndex); + try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { - SearchRequest searchRequest = new SearchRequest(resourceSharingIndex); + // Search the RESOURCE INDEX directly (not the *-sharing index) + SearchRequest searchRequest = new SearchRequest(resourceIndex); searchRequest.scroll(scroll); - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); - - boolQuery.must(actionGroupQuery); + // We match any doc whose "principals" contains at least one of the entities + // e.g., "user:alice", "role:admin", "backend:ops" + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().filter(QueryBuilders.termsQuery("principals.keyword", entities)); - executeFlattenedSearchRequest(scroll, searchRequest, boolQuery, ActionListener.wrap(resourceIds -> { + executeIdCollectingSearchRequest(scroll, searchRequest, boolQuery, ActionListener.wrap(resourceIds -> { ctx.restore(); - LOGGER.debug("Found {} documents matching the criteria in {}", resourceIds.size(), resourceSharingIndex); + LOGGER.debug("Found {} accessible resources in {}", resourceIds.size(), resourceIndex); listener.onResponse(resourceIds); - }, exception -> { LOGGER.error("Search failed for resourceIndex={}, entities={}", resourceIndex, entities, exception); listener.onFailure(exception); - })); } } + /** + * Executes a search request against the resource index and collects _id values (resource IDs) using scroll. + * + * @param scroll Search scroll context + * @param searchRequest Initial search request + * @param query Query builder for the request + * @param listener Listener to receive the collected resource IDs + */ + private void executeIdCollectingSearchRequest( + Scroll scroll, + SearchRequest searchRequest, + AbstractQueryBuilder> query, + ActionListener> listener + ) { + SearchSourceBuilder ssb = new SearchSourceBuilder().query(query).size(1000).fetchSource(false); // we only need _id + + searchRequest.source(ssb); + + StepListener searchStep = new StepListener<>(); + client.search(searchRequest, searchStep); + + searchStep.whenComplete(initialResponse -> { + Set collectedResourceIds = new HashSet<>(); + String scrollId = initialResponse.getScrollId(); + processScrollIds(collectedResourceIds, scroll, scrollId, initialResponse.getHits().getHits(), listener); + }, listener::onFailure); + } + + /** + * Recursively processes scroll results and collects hit IDs. + * + * @param collectedResourceIds Internal accumulator for resource IDs + * @param scroll Scroll context + * @param scrollId Scroll ID + * @param hits Search hits + * @param listener Listener to receive final set of resource IDs + */ + private void processScrollIds( + Set collectedResourceIds, + Scroll scroll, + String scrollId, + SearchHit[] hits, + ActionListener> listener + ) { + if (hits == null || hits.length == 0) { + clearScroll(scrollId, ActionListener.wrap(ignored -> listener.onResponse(collectedResourceIds), listener::onFailure)); + return; + } + + for (SearchHit hit : hits) { + // Resource ID is the document _id in the resource index + collectedResourceIds.add(hit.getId()); + } + + SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId).scroll(scroll); + client.searchScroll( + scrollRequest, + ActionListener.wrap( + scrollResponse -> processScrollIds( + collectedResourceIds, + scroll, + scrollResponse.getScrollId(), + scrollResponse.getHits().getHits(), + listener + ), + e -> clearScroll(scrollId, ActionListener.wrap(ignored -> listener.onFailure(e), ex -> { + e.addSuppressed(ex); + listener.onFailure(e); + })) + ) + ); + } + /** * Fetches a specific resource sharing document by its resource ID and system resourceIndex. * This method performs an exact match search and parses the result into a ResourceSharing object. @@ -831,63 +901,6 @@ private void executeSearchRequest( }, listener::onFailure); } - /** - * Executes a multi-clause query in a flattened fashion to boost performance by almost 20x for large queries. - * This is specifically to replace multi-match queries for wild-card expansions. - * @param scroll Search scroll context - * @param searchRequest Initial search request - * @param filterQuery Query builder for the request - * @param listener Listener to receive the collected resource IDs - */ - private void executeFlattenedSearchRequest( - Scroll scroll, - SearchRequest searchRequest, - BoolQueryBuilder filterQuery, - ActionListener> listener - ) { - // Painless script to emit all share_with principals - String scriptSource = """ - // handle shared - if (params._source.share_with instanceof Map) { - for (def grp : params._source.share_with.values()) { - if (grp.users instanceof List) { - for (u in grp.users) { - emit("user:" + u); - } - } - if (grp.roles instanceof List) { - for (r in grp.roles) { - emit("role:" + r); - } - } - if (grp.backend_roles instanceof List) { - for (b in grp.backend_roles) { - emit("backend:" + b); - } - } - } - } - """; - - Script script = new Script(ScriptType.INLINE, "painless", scriptSource, Map.of()); - - SearchSourceBuilder ssb = new SearchSourceBuilder().derivedField( - "all_shared_principals", // flattened runtime field - "keyword", // type - script - ).query(filterQuery).size(1000).fetchSource(new String[] { "resource_id" }, null); - - searchRequest.source(ssb); - - StepListener searchStep = new StepListener<>(); - client.search(searchRequest, searchStep); - searchStep.whenComplete(initialResponse -> { - Set collectedResourceIds = new HashSet<>(); - String scrollId = initialResponse.getScrollId(); - processScrollResults(collectedResourceIds, scroll, scrollId, initialResponse.getHits().getHits(), listener); - }, listener::onFailure); - } - /** * Recursively processes scroll results and collects resource IDs. * From 8bff6f38a6bf3e08e9f827967550c1e3c5b19d5f Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 27 Aug 2025 15:52:03 -0400 Subject: [PATCH 6/9] Add to CHANGELOG Signed-off-by: Craig Perkins --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57bb0d7db8..5b3c02cdbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Enhancements +- [Resource Sharing] Keep track of list of principals for which sharable resource is visible for searching ([#5596](https://github.com/opensearch-project/security/pull/5596)) + ### Bug Fixes -* Added new option skip_users to client cert authenticator (clientcert_auth_domain.http_authenticator.config.skip_users in config.yml)([#4378](https://github.com/opensearch-project/security/pull/5525)) -* [Resource Sharing] Fixes accessible resource ids search by marking created_by.user field as keyword search instead of text ([#5574](https://github.com/opensearch-project/security/pull/5574)) -* [Resource Sharing] Reverts @Inject pattern usage for ResourceSharingExtension to client accessor pattern. ([#5576](https://github.com/opensearch-project/security/pull/5576)) +- Added new option skip_users to client cert authenticator (clientcert_auth_domain.http_authenticator.config.skip_users in config.yml)([#4378](https://github.com/opensearch-project/security/pull/5525)) +- [Resource Sharing] Fixes accessible resource ids search by marking created_by.user field as keyword search instead of text ([#5574](https://github.com/opensearch-project/security/pull/5574)) +- [Resource Sharing] Reverts @Inject pattern usage for ResourceSharingExtension to client accessor pattern. ([#5576](https://github.com/opensearch-project/security/pull/5576)) ### Refactoring From fee622bdc90378d1420adc8804e330ad93710a27 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 29 Aug 2025 14:08:09 -0400 Subject: [PATCH 7/9] Rename to all_shared_principals Signed-off-by: Craig Perkins --- .../security/resources/ResourceSharingIndexHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java index 3ec62f0e8a..a5ffbfa3e2 100644 --- a/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java +++ b/src/main/java/org/opensearch/security/resources/ResourceSharingIndexHandler.java @@ -160,7 +160,7 @@ public void updateResourceVisibility( try (ThreadContext.StoredContext ctx = this.threadPool.getThreadContext().stashContext()) { UpdateRequest ur = client.prepareUpdate(resourceIndex, resourceId) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .setDoc(Map.of("principals", principals)) + .setDoc(Map.of("all_shared_principals", principals)) .setId(resourceId) .request(); @@ -345,7 +345,8 @@ public void fetchAccessibleResourceIds( // We match any doc whose "principals" contains at least one of the entities // e.g., "user:alice", "role:admin", "backend:ops" - BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().filter(QueryBuilders.termsQuery("principals.keyword", entities)); + BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() + .filter(QueryBuilders.termsQuery("all_shared_principals.keyword", entities)); executeIdCollectingSearchRequest(scroll, searchRequest, boolQuery, ActionListener.wrap(resourceIds -> { ctx.restore(); From a9603909a79959a1ba25dfab6720b5e01e50cc89 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 2 Sep 2025 11:43:14 -0400 Subject: [PATCH 8/9] Fix CHANGELOG Signed-off-by: Craig Perkins --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d95197c2..149d32253d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Bug Fixes -- Added new option skip_users to client cert authenticator (clientcert_auth_domain.http_authenticator.config.skip_users in config.yml)([#4378](https://github.com/opensearch-project/security/pull/5525)) -- [Resource Sharing] Fixes accessible resource ids search by marking created_by.user field as keyword search instead of text ([#5574](https://github.com/opensearch-project/security/pull/5574)) -- [Resource Sharing] Reverts @Inject pattern usage for ResourceSharingExtension to client accessor pattern. ([#5576](https://github.com/opensearch-project/security/pull/5576)) * Added new option skip_users to client cert authenticator (clientcert_auth_domain.http_authenticator.config.skip_users in config.yml)([#4378](https://github.com/opensearch-project/security/pull/5525)) * [Resource Sharing] Fixes accessible resource ids search by marking created_by.user field as keyword search instead of text ([#5574](https://github.com/opensearch-project/security/pull/5574)) * [Resource Sharing] Reverts @Inject pattern usage for ResourceSharingExtension to client accessor pattern. ([#5576](https://github.com/opensearch-project/security/pull/5576)) From 9ff8918543e5f7b4ba0c44348dc4f023858e165d Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 2 Sep 2025 14:15:57 -0400 Subject: [PATCH 9/9] Remove sysout Signed-off-by: Craig Perkins --- .../actions/transport/SearchResourceTransportAction.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java index f29ef3e221..0add62ba8d 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/SearchResourceTransportAction.java @@ -70,7 +70,6 @@ protected void doExecute(Task task, SearchRequest request, ActionListener listener) { SearchSourceBuilder src = request.source() != null ? request.source() : new SearchSourceBuilder(); ActionListener> idsListener = ActionListener.wrap(resourceIds -> { - System.out.println("resourceIds to filter: " + resourceIds); mergeAccessibleFilter(src, resourceIds); request.source(src); nodeClient.search(request, listener);