diff --git a/sample-extension-plugin/build.gradle b/sample-extension-plugin/build.gradle index 8e1e69c498..d601890483 100644 --- a/sample-extension-plugin/build.gradle +++ b/sample-extension-plugin/build.gradle @@ -16,7 +16,7 @@ import java.util.concurrent.Callable opensearchplugin { name 'opensearch-security-sample-extension' - description 'Sample plugin that extends OpenSearch Security Resource Sharing plugin' + description 'Sample plugin that extends OpenSearch Security Resource Sharing Extension' classname 'org.opensearch.security.sampleextension.SampleExtensionPlugin' extendedPlugins = ['opensearch-security'] } diff --git a/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java b/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java index c18e000a12..3c3f8c281c 100644 --- a/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java +++ b/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java @@ -33,7 +33,7 @@ public class SampleExtensionPluginTests { @ClassRule - public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS) + public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) .plugin(SampleExtensionPlugin.class) .anonymousAuth(true) .authc(AUTHC_HTTPBASIC_INTERNAL) @@ -50,6 +50,7 @@ public void testSecurityRoles() throws Exception { assertThat(response.getTextFromJsonBody("/user_name"), equalTo("admin")); System.out.println("Response: " + response.getBody()); HttpResponse pluginsResponse = client.get("_cat/plugins?s=component&h=name,component,version,description"); + System.out.println("pluginsResponse: " + pluginsResponse.getBody()); assertThat(pluginsResponse.getBody(), containsString("org.opensearch.security.OpenSearchSecurityPlugin")); assertThat(pluginsResponse.getBody(), containsString("org.opensearch.security.sampleextension.SampleExtensionPlugin")); } diff --git a/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java b/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java index 5067708bc6..aa48c54d7f 100644 --- a/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java +++ b/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java @@ -77,8 +77,6 @@ public class SampleExtensionPlugin extends Plugin implements ActionPlugin, Syste private Client client; - private final SampleResourceSharingServiceProvider provider = new SampleResourceSharingServiceProvider(); - @Override public Collection createComponents( Client client, @@ -94,11 +92,15 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; - if (provider.get() == null) { - provider.set(new DefaultResourceSharingService<>(client, RESOURCE_INDEX_NAME, new SampleResourceFactory())); + if (SampleResourceSharingServiceProvider.getInstance().get() == null) { + System.out.println("Using DefaultResourceSharingService"); + SampleResourceSharingServiceProvider.getInstance() + .set(new DefaultResourceSharingService<>(client, RESOURCE_INDEX_NAME, new SampleResourceFactory())); } - System.out.println("provider: " + provider); - return List.of(provider); + System.out.println( + "SampleResourceSharingServiceProvider.getInstance(): " + SampleResourceSharingServiceProvider.getInstance().get() + ); + return List.of(SampleResourceSharingServiceProvider.getInstance()); } @Override @@ -155,6 +157,6 @@ public ResourceFactory getResourceFactory() { @SuppressWarnings("unchecked") @Override public void assignResourceSharingService(ResourceSharingService service) { - provider.set((ResourceSharingService) service); + SampleResourceSharingServiceProvider.getInstance().set((ResourceSharingService) service); } } diff --git a/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/resource/SampleResourceSharingServiceProvider.java b/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/resource/SampleResourceSharingServiceProvider.java index d375e9bc12..7540c07977 100644 --- a/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/resource/SampleResourceSharingServiceProvider.java +++ b/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/resource/SampleResourceSharingServiceProvider.java @@ -1,5 +1,10 @@ package org.opensearch.security.sampleextension.resource; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + import org.opensearch.common.inject.Provider; import org.opensearch.security.spi.ResourceSharingService; @@ -12,6 +17,19 @@ public final class SampleResourceSharingServiceProvider implements Provider resourceSharingService; + private static final Map instances = new ConcurrentHashMap<>(); + + private SampleResourceSharingServiceProvider() {} + + @SuppressWarnings("removal") + public static SampleResourceSharingServiceProvider getInstance() { + ClassLoader classLoader = AccessController.doPrivileged( + (PrivilegedAction) () -> Thread.currentThread().getContextClassLoader() + ); + instances.computeIfAbsent(classLoader, cl -> new SampleResourceSharingServiceProvider()); + return instances.get(classLoader); + } + /** * Sets the resource sharing service implementation. * This method is thread-safe and ensures the service is only set once. diff --git a/src/main/java/org/opensearch/security/resource/SecurityResourceSharingService.java b/src/main/java/org/opensearch/security/resource/SecurityResourceSharingService.java index 31fe30727c..967f023754 100644 --- a/src/main/java/org/opensearch/security/resource/SecurityResourceSharingService.java +++ b/src/main/java/org/opensearch/security/resource/SecurityResourceSharingService.java @@ -35,6 +35,7 @@ import org.opensearch.security.spi.ResourceSharingService; import org.opensearch.security.spi.ShareWith; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.support.WildcardMatcher; import org.opensearch.security.user.User; import static org.opensearch.security.resource.ResourceSharingListener.RESOURCE_SHARING_INDEX; @@ -50,50 +51,24 @@ public SecurityResourceSharingService(Client client, String resourceIndex, Resou this.resourceFactory = resourceFactory; } -// private boolean hasPermissionsFor(User authenticatedUser, Resource resource) { -// // TODO Complete this function. The user has permissions if either of the conditions are true: -// -// // 1. The resource_user is the currently authenticated user -// // 2. The resource has been shared with the authenticated user -// // 3. The resource has been shared with a backend role that the authenticated user has -// if (authenticatedUser.getName().equals(resource.getResourceUser().getName())) { -// return true; -// } -// try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashContext()) { -// SearchRequest searchRequest = new SearchRequest(RESOURCE_SHARING_INDEX); -// SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); -// BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() -// .must(QueryBuilders.termQuery("resource_index", resourceIndex)) -// .must(QueryBuilders.termQuery("resource_id", resource.getResourceId())); -// -// searchSourceBuilder.query(boolQuery); -// searchSourceBuilder.size(1); // Limit to 1 result -// searchRequest.source(searchSourceBuilder); -// -// ActionListener searchListener = new ActionListener() { -// @Override -// public void onResponse(SearchResponse searchResponse) { -// SearchHit[] hits = searchResponse.getHits().getHits(); -// if (hits.length > 0) { -// SearchHit hit = hits[0]; -// T resource = resourceFactory.createResource(); -// resource.fromSource(hit.getId(), hit.getSourceAsMap()); -// getResourceListener.onResponse(resource); -// } else { -// getResourceListener.onFailure(new ResourceNotFoundException("Resource not found")); -// } -// } -// -// @Override -// public void onFailure(Exception e) { -// throw new OpenSearchException("Caught exception while loading resources: " + e.getMessage()); -// } -// }; -// -// client.search(searchRequest, searchListener); -// } -// return false; -// } + private boolean hasPermissionsFor(User authenticatedUser, Resource resource, ShareWith sharedWith) { + System.out.println("hasPermissionsFor: " + authenticatedUser + " " + resource + " " + sharedWith); + // 1. The resource_user is the currently authenticated user + // 2. The resource has been shared with the authenticated user + // 3. The resource has been shared with a backend role that the authenticated user has + if (authenticatedUser.getName().equals(resource.getResourceUser().getName())) { + return true; + } + WildcardMatcher userMatcher = WildcardMatcher.from(sharedWith.getUsers()); + if (userMatcher.test(authenticatedUser.getName())) { + return true; + } + WildcardMatcher backendRoleMatcher = WildcardMatcher.from(sharedWith.getBackendRoles()); + if (authenticatedUser.getRoles().stream().anyMatch(backendRoleMatcher::test)) { + return true; + } + return false; + } @SuppressWarnings("unchecked") @Override @@ -200,6 +175,7 @@ public void getResource(String resourceId, ActionListener getResourceListener public void onResponse(GetResponse getResponse) { T resource = resourceFactory.createResource(); resource.fromSource(getResponse.getId(), getResponse.getSourceAsMap()); + System.out.println("finishGetResourceIfUserIsAllowed"); finishGetResourceIfUserIsAllowed(resource, authenticatedUser, getResourceListener); } @@ -217,8 +193,8 @@ private void finishGetResourceIfUserIsAllowed(T resource, User authenticatedUser SearchRequest searchRequest = new SearchRequest(RESOURCE_SHARING_INDEX); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("resource_index", resourceIndex)) - .must(QueryBuilders.termQuery("resource_id", resource.getResourceId())); + .must(QueryBuilders.termQuery("resource_index", resourceIndex)) + .must(QueryBuilders.termQuery("resource_id", resource.getResourceId())); searchSourceBuilder.query(boolQuery); searchSourceBuilder.size(1); // Limit to 1 result @@ -231,7 +207,11 @@ public void onResponse(SearchResponse searchResponse) { if (hits.length > 0) { SearchHit hit = hits[0]; ShareWith sharedWith = ShareWith.fromSource(hit.getSourceAsMap()); - getResourceListener.onResponse(resource); + if (hasPermissionsFor(authenticatedUser, resource, sharedWith)) { + getResourceListener.onResponse(resource); + } else { + getResourceListener.onFailure(new OpenSearchException("User is not authorized to access this resource")); + } } else { getResourceListener.onFailure(new ResourceNotFoundException("Resource not found")); }