diff --git a/sample-extension-plugin/src/test/java/org/opensearch/security/sampleextension/SampleExtensionPluginIT.java b/sample-extension-plugin/src/test/java/org/opensearch/security/sampleextension/SampleExtensionPluginIT.java index b60cb00aec..9291fdbf43 100644 --- a/sample-extension-plugin/src/test/java/org/opensearch/security/sampleextension/SampleExtensionPluginIT.java +++ b/sample-extension-plugin/src/test/java/org/opensearch/security/sampleextension/SampleExtensionPluginIT.java @@ -13,15 +13,15 @@ import java.util.Base64; import java.util.List; import java.util.Map; +import java.util.Optional; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hc.core5.http.io.entity.StringEntity; import org.junit.Assert; import org.opensearch.client.Request; import org.opensearch.client.RequestOptions; import org.opensearch.client.Response; +import org.opensearch.common.collect.Tuple; import org.opensearch.common.xcontent.LoggingDeprecationHandler; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.core.xcontent.NamedXContentRegistry; @@ -54,55 +54,76 @@ public void testPluginsAreInstalled() throws IOException { ); } - public void testCreateSampleResource() throws IOException, InterruptedException { - String strongPassword = "myStrongPassword123!"; - Request createUserRequest = new Request("PUT", "/_opendistro/_security/api/internalusers/craig"); - createUserRequest.setJsonEntity("{\"password\":\"" + strongPassword + "\",\"backend_roles\":[\"admin\"]}"); - client().performRequest(createUserRequest); - - RequestOptions.Builder requestOptions = RequestOptions.DEFAULT.toBuilder(); - requestOptions.setWarningsHandler((warnings) -> false); + private static Map createSampleResource(String name, Optional> credentials) throws IOException { + RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder(); + options.setWarningsHandler((warnings) -> false); + credentials.ifPresent( + stringStringTuple -> options.addHeader( + "Authorization", + "Basic " + + Base64.getEncoder() + .encodeToString((stringStringTuple.v1() + ":" + stringStringTuple.v2()).getBytes(StandardCharsets.UTF_8)) + ) + ); - Request createRequest = new Request("POST", "/_plugins/resource_sharing_example/resource"); - createRequest.setEntity(new StringEntity("{\"name\":\"ExampleResource1\"}")); - createRequest.setOptions(requestOptions); - Response response = client().performRequest(createRequest); + Request request = new Request("POST", "/_plugins/resource_sharing_example/resource"); + request.setEntity(new StringEntity("{\"name\":\"" + name + "\"}")); + request.setOptions(options); + Response response = client().performRequest(request); Map createResourceResponse = JsonXContent.jsonXContent.createParser( NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getEntity().getContent() ).mapStrings(); System.out.println("createResourceResponse: " + createResourceResponse); + return createResourceResponse; + } - Request createRequest2 = new Request("POST", "/_plugins/resource_sharing_example/resource"); - createRequest2.setEntity(new StringEntity("{\"name\":\"ExampleResource2\"}")); - RequestOptions.Builder requestOptions2 = RequestOptions.DEFAULT.toBuilder(); - requestOptions2.setWarningsHandler((warnings) -> false); - requestOptions2.addHeader( - "Authorization", - "Basic " + Base64.getEncoder().encodeToString(("craig:" + strongPassword).getBytes(StandardCharsets.UTF_8)) + private static Map updateSharing(String resourceId, String payload, Optional> credentials) + throws IOException { + RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder(); + options.setWarningsHandler((warnings) -> false); + credentials.ifPresent( + stringStringTuple -> options.addHeader( + "Authorization", + "Basic " + + Base64.getEncoder() + .encodeToString((stringStringTuple.v1() + ":" + stringStringTuple.v2()).getBytes(StandardCharsets.UTF_8)) + ) ); - createRequest2.setOptions(requestOptions2); - Response response2 = client().performRequest(createRequest2); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode indexResponseNode = objectMapper.readTree(response2.getEntity().getContent()); - - String resourceId = indexResponseNode.get("resourceId").asText(); - Map createResourceResponse2 = JsonXContent.jsonXContent.createParser( + Request updateSharingRequest = new Request("PUT", "/_plugins/_security/resource/sample_resource/" + resourceId + "/share_with"); + updateSharingRequest.setEntity(new StringEntity(payload)); + options.addHeader("Content-Type", "application/json"); + updateSharingRequest.setOptions(options); + Response updateResponse = client().performRequest(updateSharingRequest); + Map updateSharingResponse = JsonXContent.jsonXContent.createParser( NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, - response2.getEntity().getContent() + updateResponse.getEntity().getContent() ).mapStrings(); - System.out.println("createResourceResponse2: " + createResourceResponse2); + return updateSharingResponse; + } + + public void testCreateSampleResource() throws IOException, InterruptedException { + RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder(); + options.setWarningsHandler((warnings) -> false); + + String strongPassword = "myStrongPassword123!"; + Request createUserRequest = new Request("PUT", "/_opendistro/_security/api/internalusers/testuser"); + createUserRequest.setJsonEntity("{\"password\":\"" + strongPassword + "\",\"backend_roles\":[\"admin\"]}"); + client().performRequest(createUserRequest); + + createSampleResource("ExampleResource1", Optional.empty()); + String resourceId = createSampleResource("ExampleResource2", Optional.of(Tuple.tuple("testuser", strongPassword))).get( + "resourceId" + ); // Sleep to give ResourceSharingListener time to create the .resource-sharing index Thread.sleep(1000); Request listRequest = new Request("GET", "/_plugins/resource_sharing_example/resource"); - listRequest.setOptions(requestOptions); + listRequest.setOptions(options); Response listResponse = client().performRequest(listRequest); - JsonNode resNode = objectMapper.readTree(listResponse.getEntity().getContent()); - System.out.println("resNode: " + resNode); Map listResourceResponse = JsonXContent.jsonXContent.createParser( NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, @@ -111,7 +132,7 @@ public void testCreateSampleResource() throws IOException, InterruptedException System.out.println("listResourceResponse: " + listResourceResponse); Request resourceSharingRequest = new Request("POST", "/.sample_extension_resources/_search"); - resourceSharingRequest.setOptions(requestOptions); + resourceSharingRequest.setOptions(options); Response resourceSharingResponse = adminClient().performRequest(resourceSharingRequest); Map resourceSharingResponseMap = JsonXContent.jsonXContent.createParser( NamedXContentRegistry.EMPTY, @@ -120,18 +141,11 @@ public void testCreateSampleResource() throws IOException, InterruptedException ).map(); System.out.println("sampleResources: " + resourceSharingResponseMap); - Request updateSharingRequest = new Request("PUT", "/_plugins/_security/resource/sample_resource/" + resourceId + "/share_with"); - updateSharingRequest.setEntity( - new StringEntity("{\"share_with\":{\"users\": [\"admin\"], \"backend_roles\": [], \"allowed_actions\": [\"*\"]}}") + Map updateSharingResponse = updateSharing( + resourceId, + "{\"share_with\":{\"users\": [\"admin\"], \"backend_roles\": [], \"allowed_actions\": [\"*\"]}}", + Optional.of(Tuple.tuple("testuser", strongPassword)) ); - requestOptions.addHeader("Content-Type", "application/json"); - updateSharingRequest.setOptions(requestOptions); - Response updateResponse = client().performRequest(updateSharingRequest); - Map updateSharingResponse = JsonXContent.jsonXContent.createParser( - NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, - updateResponse.getEntity().getContent() - ).mapStrings(); System.out.println("updateSharingResponse: " + updateSharingResponse); Thread.sleep(1000); diff --git a/spi/src/main/java/org/opensearch/security/spi/actions/resource/get/GetResourceTransportAction.java b/spi/src/main/java/org/opensearch/security/spi/actions/resource/get/GetResourceTransportAction.java index a8150c463c..ef1c1bb413 100644 --- a/spi/src/main/java/org/opensearch/security/spi/actions/resource/get/GetResourceTransportAction.java +++ b/spi/src/main/java/org/opensearch/security/spi/actions/resource/get/GetResourceTransportAction.java @@ -15,6 +15,7 @@ import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; +import org.opensearch.ResourceNotFoundException; import org.opensearch.action.get.GetRequest; import org.opensearch.action.get.GetResponse; import org.opensearch.action.support.ActionFilters; @@ -82,6 +83,11 @@ private void getResource(GetResourceRequest request, ActionListener getListener = new ActionListener<>() { @Override public void onResponse(GetResponse getResponse) { + System.out.println("Get response: " + getResponse.isExists()); + if (!getResponse.isExists()) { + getResourceListener.onFailure(new ResourceNotFoundException("Resource not found")); + return; + } try { XContentParser parser = XContentHelper.createParser( xContentRegistry, @@ -91,27 +97,6 @@ public void onResponse(GetResponse getResponse) { ); T resource = resourceParser.parse(parser, getResponse.getId()); System.out.println("resource: " + resource); - // ActionListener shareListener = new ActionListener<>() { - // @Override - // public void onResponse(Boolean isShared) { - // if (isShared) { - // getResourceListener.onResponse(resource); - // } else { - // getResourceListener.onFailure( - // new OpenSearchException("User is not authorized to access this resource") - // ); - // } - // } - // - // @Override - // public void onFailure(Exception e) { - // getResourceListener.onFailure( - // new OpenSearchException("Failed to check sharing status: " + e.getMessage(), e) - // ); - // } - // }; - // - // resourceSharingService.isSharedWithCurrentUser(request.getResourceId(), shareListener); getResourceListener.onResponse(resource); } catch (IOException e) { throw new OpenSearchException("Caught exception while loading resources: " + e.getMessage()); diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index e656b74bc9..dd80854cbe 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -727,7 +727,8 @@ public void onIndexModule(IndexModule indexModule) { evaluator, dlsFlsValve::getCurrentConfig, dlsFlsBaseContext, - namedXContentRegistry.get() + namedXContentRegistry.get(), + sharableResourceIndices ) ); System.out.println("this.indicesToListen: " + this.sharableResourceIndices); diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index 2b03df47c5..55ab3c3858 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -138,11 +138,8 @@ private class DlsGetEvaluator { private final boolean hasDeletions; public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyDlsHere) throws IOException { - // System.out.println("DlsGetEvaluator"); - // System.out.println("dlsQuery: " + dlsQuery); - // System.out.println("applyDlsHere: " + applyDlsHere); if (dlsQuery != null && applyDlsHere) { - // if (dlsQuery != null) { + System.out.println("Applying dls query on get request: " + dlsQuery.toString()); // borrowed from Apache Lucene (Copyright Apache Software Foundation (ASF)) // https://github.com/apache/lucene-solr/blob/branch_6_3/lucene/misc/src/java/org/apache/lucene/index/PKIndexSplitter.java final IndexSearcher searcher = new IndexSearcher(DlsFlsFilterLeafReader.this); diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index 02be89edf1..1320be5003 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -437,7 +437,6 @@ public void handleSearchContext(SearchContext searchContext, ThreadPool threadPo + "}" + "}" ); - System.out.println("queryString: " + queryString); dlsRestriction = new DlsRestriction( List.of(new DocumentPrivileges.RenderedDlsQuery(parseQuery(queryString, namedXContentRegistry), queryString)) ); diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java index 9a802114cf..5772666ec1 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java @@ -19,6 +19,7 @@ import java.util.Set; import java.util.function.LongSupplier; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -55,6 +56,7 @@ import org.opensearch.security.privileges.dlsfls.FieldMasking; import org.opensearch.security.privileges.dlsfls.FieldPrivileges; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapper { @@ -71,6 +73,7 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SystemIndexSearcherWrapp private final Supplier dlsFlsProcessedConfigSupplier; private final DlsFlsBaseContext dlsFlsBaseContext; private final NamedXContentRegistry xContentRegistry; + private final Set sharableResourceIndices; public SecurityFlsDlsIndexSearcherWrapper( final IndexService indexService, @@ -82,7 +85,8 @@ public SecurityFlsDlsIndexSearcherWrapper( final PrivilegesEvaluator evaluator, final Supplier dlsFlsProcessedConfigSupplier, final DlsFlsBaseContext dlsFlsBaseContext, - final NamedXContentRegistry xContentRegistry + final NamedXContentRegistry xContentRegistry, + final Set sharableResourceIndices ) { super(indexService, settings, adminDNs, evaluator); Set metadataFieldsCopy; @@ -106,6 +110,7 @@ public SecurityFlsDlsIndexSearcherWrapper( this.indexService = indexService; this.auditlog = auditlog; this.xContentRegistry = xContentRegistry; + this.sharableResourceIndices = sharableResourceIndices; final boolean allowNowinDlsQueries = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS, false); if (allowNowinDlsQueries) { nowInMillis = () -> System.currentTimeMillis(); @@ -129,7 +134,7 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm log.trace("dlsFlsWrap(); index: {}; privilegeEvaluationContext: {}", index.getName(), privilegesEvaluationContext); } - if ((isAdmin || privilegesEvaluationContext == null) && !shardId.getIndexName().equals(".sample_extension_resources")) { + if ((isAdmin || privilegesEvaluationContext == null) && !sharableResourceIndices.contains(shardId.getIndexName())) { return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( reader, FieldPrivileges.FlsRule.ALLOW_ALL, @@ -144,62 +149,15 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm ); } - System.out.println("shardID: " + shardId.getIndexName()); - - if (shardId.getIndexName().equals(".sample_extension_resources")) { - System.out.println("dlsFlsWarp: " + shardId.getIndexName() + " is .sample_extension_resources"); - } - try { DlsFlsProcessedConfig config = this.dlsFlsProcessedConfigSupplier.get(); DlsRestriction dlsRestriction; - if (shardId.getIndex().getName().equals(".sample_extension_resources")) { - String queryString = """ - { "bool": { - "should": [ - { - "term": { - "resource_user.name": "resource_sharing_test_user" - } - }, - { - "terms": { - "share_with.backend_roles": ["admin"] - } - }, - { - "term": { - "share_with.users": "resource_sharing_test_user" - } - } - ], - "minimum_should_match": 1 - } - } - """; - dlsRestriction = new DlsRestriction( - List.of(new DocumentPrivileges.RenderedDlsQuery(parseQuery(queryString, xContentRegistry), queryString)) - ); - QueryShardContext queryShardContext = this.indexService.newQueryShardContext(shardId.getId(), null, nowInMillis, null); - Query dlsQuery = new ConstantScoreQuery(dlsRestriction.toBooleanQueryBuilder(queryShardContext, null).build()); - String action = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ACTION_NAME); - System.out.println("action: " + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ACTION_NAME)); - if (action.startsWith("indices:data/read/search")) { - return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( - reader, - FieldPrivileges.FlsRule.ALLOW_ALL, - dlsQuery, - indexService, - threadContext, - clusterService, - auditlog, - FieldMasking.FieldMaskingRule.ALLOW_ALL, - shardId, - metaFields - ); - } else { + if (sharableResourceIndices.contains(shardId.getIndexName()) + && threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER) != null) { + User user = (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER); + if (adminDns.isAdmin(user)) { return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( reader, FieldPrivileges.FlsRule.ALLOW_ALL, @@ -213,6 +171,60 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm metaFields ); } + + String username = user.getName(); + Set backendRoles = user.getRoles(); + + // Convert backend roles to JSON array string + String backendRolesJson = backendRoles.stream().map(role -> "\"" + role + "\"").collect(Collectors.joining(", ")); + + String queryString = String.format( + "{ \"bool\": {" + + "\"should\": [" + + "{" + + "\"term\": {" + + "\"resource_user.name\": \"" + + username + + "\"" + + "}" + + "}," + + "{" + + "\"terms\": {" + + "\"share_with.backend_roles\": [" + + backendRolesJson + + "]" + + "}" + + "}," + + "{" + + "\"term\": {" + + "\"share_with.users\": \"" + + username + + "\"" + + "}" + + "}" + + "]," + + "\"minimum_should_match\": 1" + + "}" + + "}" + ); + dlsRestriction = new DlsRestriction( + List.of(new DocumentPrivileges.RenderedDlsQuery(parseQuery(queryString, xContentRegistry), queryString)) + ); + QueryShardContext queryShardContext = this.indexService.newQueryShardContext(shardId.getId(), null, nowInMillis, null); + Query dlsQuery = new ConstantScoreQuery(dlsRestriction.toBooleanQueryBuilder(queryShardContext, null).build()); + String action = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ACTION_NAME); + return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( + reader, + FieldPrivileges.FlsRule.ALLOW_ALL, + action.startsWith("indices:data/read/get") ? dlsQuery : null, + indexService, + threadContext, + clusterService, + auditlog, + FieldMasking.FieldMaskingRule.ALLOW_ALL, + shardId, + metaFields + ); } if (!this.dlsFlsBaseContext.isDlsDoneOnFilterLevel()) { @@ -278,11 +290,9 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm ); } catch (PrivilegesEvaluationException e) { - System.out.println("caught PrivilegesEvaluationException: " + e.getMessage()); log.error("Error while evaluating DLS/FLS for {}", this.index.getName(), e); throw new OpenSearchException("Error while evaluating DLS/FLS", e); } catch (PrivilegesConfigurationValidationException e) { - System.out.println("caught exception: " + e.getMessage()); throw new RuntimeException(e); } } diff --git a/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java index b87c92c356..203788af04 100644 --- a/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java @@ -58,7 +58,7 @@ public class SystemIndexSearcherWrapper implements CheckedFunction