From 909a85b7f069336c89471bc8aa94bfd464086563 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 27 Aug 2024 14:34:25 -0400 Subject: [PATCH 01/51] Adds a new plugin type named ResourcePlugin and relevant base classes Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/CreatedBy.java | 48 +++++++++ .../accesscontrol/resources/Resource.java | 101 ++++++++++++++++++ .../resources/ResourceSharing.java | 61 +++++++++++ .../accesscontrol/resources/ShareWith.java | 23 ++++ .../opensearch/plugins/ResourcePlugin.java | 73 +++++++++++++ 5 files changed, 306 insertions(+) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java create mode 100644 server/src/main/java/org/opensearch/plugins/ResourcePlugin.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java new file mode 100644 index 0000000000000..01c61e82ed5f4 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java @@ -0,0 +1,48 @@ +/* + * 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.accesscontrol.resources; + +/** + * This class contains information on the creator of a resource. + * Creator can either be a user or a backend_role. + * + * @opensearch.experimental + */ +public class CreatedBy { + + private String user; + + private String backendRole; + + public CreatedBy(String user, String backendRole) { + this.user = user; + this.backendRole = backendRole; + } + + public String getBackendRole() { + return backendRole; + } + + public void setBackendRole(String backendRole) { + this.backendRole = backendRole; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + @Override + public String toString() { + return "CreatedBy {" + "user='" + user + '\'' + ", backendRole='" + backendRole + '\'' + '}'; + } +} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java new file mode 100644 index 0000000000000..4212cb630cced --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java @@ -0,0 +1,101 @@ +/* + * 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.accesscontrol.resources; + +import java.util.Objects; + +/** + * A document in .resource_sharing index. + * Holds information about the resource (obtained from defining plugin's meta-data), + * the index which defines the resources, the creator of the resource, + * and the information on whom this resource is shared with. + * + * @opensearch.experimental + */ +public class Resource { + + private String sourceIdx; + + private String resourceId; + + private CreatedBy createdBy; + + private ResourceSharing sharedWith; + + public Resource(String sourceIdx, String resourceId, CreatedBy createdBy, ResourceSharing sharedWith) { + this.sourceIdx = sourceIdx; + this.resourceId = resourceId; + this.createdBy = createdBy; + this.sharedWith = sharedWith; + } + + 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 ResourceSharing getSharedWith() { + return sharedWith; + } + + public void setSharedWith(ResourceSharing sharedWith) { + this.sharedWith = sharedWith; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Resource resource = (Resource) o; + return Objects.equals(getSourceIdx(), resource.getSourceIdx()) + && Objects.equals(getResourceId(), resource.getResourceId()) + && Objects.equals(getCreatedBy(), resource.getCreatedBy()) + && Objects.equals(getSharedWith(), resource.getSharedWith()); + } + + @Override + public int hashCode() { + return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getSharedWith()); + } + + @Override + public String toString() { + return "Resource {" + + "sourceIdx='" + + sourceIdx + + '\'' + + ", resourceId='" + + resourceId + + '\'' + + ", createdBy=" + + createdBy + + ", sharedWith=" + + sharedWith + + '}'; + } +} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java new file mode 100644 index 0000000000000..331ccb5de898c --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.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.accesscontrol.resources; + +import java.util.List; + +/** + * This class contains information about whom a resource is shared with. + * It could be a user-name, a role or a backend_role. + * + * @opensearch.experimental + */ +public class ResourceSharing { + + private List users; + + private List roles; + + private List backendRoles; + + public ResourceSharing(List users, List backendRoles, List roles) { + this.users = users; + this.backendRoles = backendRoles; + this.roles = roles; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getBackendRoles() { + return backendRoles; + } + + public void setBackendRoles(List backendRoles) { + this.backendRoles = backendRoles; + } + + @Override + public String toString() { + return "ResourceSharing {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + } +} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java new file mode 100644 index 0000000000000..702081ebd9ec9 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -0,0 +1,23 @@ +/* + * 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.accesscontrol.resources; + +/** + * This enum contains the type of entities a resource can be shared with. + * + * @opensearch.experimental + */ +public enum ShareWith { + + USERS, + + ROLES, + + BACKEND_ROLES, +} diff --git a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java new file mode 100644 index 0000000000000..3a357fd16c4a9 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java @@ -0,0 +1,73 @@ +/* + * 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.plugins; + +import org.opensearch.accesscontrol.resources.Resource; +import org.opensearch.accesscontrol.resources.ShareWith; + +import java.util.List; +import java.util.Map; + +/** + * This plugin class defines usage mechanisms for plugins to interact with resources. + * User information is fetched from thread context by security plugin. + * In clusters, where security plugin is disabled these requests will be pass-through via a No-op implementation. + * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit {@linkplain this design proposal.} + * If security plugin is disabled, all resources will be considered public by default. + * TODO: add documentation around "how to use" + * + * + * + * @opensearch.experimental + */ +public interface ResourcePlugin { + + /** + * Returns all accessible resources for current user. + * + * @return list of {@link Resource} items accessible by current user. + */ + List listAccessibleResources(); + + /** + * Checks whether current user has permission to given resource. + * + * + * @param resource the resource on which access is to be checked + * @return true if current user has access, false otherwise + */ + boolean hasPermission(Resource resource); + + /** + * Adds an entity to the share-with. Resource needs to be in restricted mode. + * @param type One of the {@link ShareWith} types + * @param entities List of names with whom to share this resource with + * @return a message whether sharing was successful. + */ + String shareWith(ShareWith type, List entities); + + /** + * Revokes given permission to a resource + * + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined + * @param revokeAccess a map that contains entries of entities whose access should be revoked + * @return true if revoke was successful, false if there was a failure + */ + boolean revoke(String resourceId, String systemIndexName, Map> revokeAccess); + + /** + * Deletes an entry from .resource_sharing index + * @param resource The resource to be removed from the index + * @return true if resource record was deleted, false otherwise + */ + boolean deleteResourceSharingRecord(Resource resource); + + // TODO: Check whether methods for bulk updates are required +} From 66a849c3e548dc19e00471380956ad49a67ce14d Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 27 Aug 2024 14:34:50 -0400 Subject: [PATCH 02/51] Adds a No-op implementation of ResourcePlugin Signed-off-by: Darshit Chanpura --- .../plugins/NoOpResourcePlugin.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java new file mode 100644 index 0000000000000..c0878a3855187 --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java @@ -0,0 +1,81 @@ +/* + * 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.plugins; + +import org.opensearch.accesscontrol.resources.Resource; +import org.opensearch.accesscontrol.resources.ShareWith; + +import java.util.List; +import java.util.Map; + +/** + * This plugin class defines a no-op implementation of Resource Plugin. + * + * @opensearch.experimental + */ +public class NoOpResourcePlugin implements ResourcePlugin { + + /** + * Returns an empty list since security plugin is not defined. + * This method alone doesn't determine permissions. + * + * @return empty list + */ + @Override + public List listAccessibleResources() { + // returns an empty list since security plugin is disabled + return List.of(); + } + + /** + * Returns true since no authorization is required. + * + * @param resource the resource on which access is to be checked + * @return true + */ + public boolean hasPermission(Resource resource) { + return true; + } + + /** + * Adds an entity to the share-with. Resource needs to be in restricted mode. + * + * @param type One of the {@link ShareWith} types + * @param entities List of names with whom to share this resource with + * @return a message whether sharing was successful. + */ + public String shareWith(ShareWith type, List entities) { + + return "Unable to share as security plugin is disabled in the cluster."; + } + + /** + * Revokes access to the resource + * + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined + * @param revokeAccess a map that contains entries of entities whose access should be revoked + * @return false since no resource-sharing information is required as security plugin is disabled + */ + @Override + public boolean revoke(String resourceId, String systemIndexName, Map> revokeAccess) { + return false; + } + + /** + * Delete a resource sharing record + * @param resource The resource to be removed from the index + * @return false since security plugin is disabled + */ + @Override + public boolean deleteResourceSharingRecord(Resource resource) { + return false; + } + +} From d7169e4847e44724b659f56a587fac317f245b51 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 12:19:25 -0400 Subject: [PATCH 03/51] Adds a way to configure security plugin for resource access-control Signed-off-by: Darshit Chanpura --- .../resources/ResourceService.java | 49 +++++++++++++++++++ .../main/java/org/opensearch/node/Node.java | 8 +++ .../plugins/ResourceAccessControlPlugin.java | 17 +++++++ 3 files changed, 74 insertions(+) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java create mode 100644 server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java new file mode 100644 index 0000000000000..b95d693b07b72 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.accesscontrol.resources; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; +import org.opensearch.plugins.NoOpResourcePlugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.ResourcePlugin; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Resource access control for OpenSearch + * + * @opensearch.experimental + * */ +public class ResourceService { + private static final Logger log = LogManager.getLogger(ResourceService.class); + + private final ResourcePlugin resourcePlugin; + + public ResourceService(final List resourcePlugins) { + if (resourcePlugins.size() == 0) { + log.debug("Security plugin disabled: Using NoopResourcePlugin"); + resourcePlugin = new NoOpResourcePlugin(); + } else if (resourcePlugins.size() == 1) { + log.debug("Security plugin enabled: Using OpenSearchSecurityPlugin"); + resourcePlugin = resourcePlugins.get(0); + } else { + throw new OpenSearchException( + "Multiple resource access control plugins are not supported, found: " + + resourcePlugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(",")) + ); + } + } + + /** + * Gets the current ResourcePlugin to perform authorization + */ + public ResourcePlugin getResourceAccessControlPlugin() { + return resourcePlugin; + } +} diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index ea656af6110e5..01373adbc28df 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -41,6 +41,7 @@ import org.opensearch.OpenSearchParseException; import org.opensearch.OpenSearchTimeoutException; import org.opensearch.Version; +import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.action.ActionModule; import org.opensearch.action.ActionModule.DynamicActionRegistry; import org.opensearch.action.ActionType; @@ -212,6 +213,7 @@ import org.opensearch.plugins.Plugin; import org.opensearch.plugins.PluginsService; import org.opensearch.plugins.RepositoryPlugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; import org.opensearch.plugins.ScriptPlugin; import org.opensearch.plugins.SearchPipelinePlugin; import org.opensearch.plugins.SearchPlugin; @@ -1058,6 +1060,11 @@ protected Node( ); modules.add(actionModule); + final List resourceAccessControlPlugins = pluginsService.filterPlugins( + ResourceAccessControlPlugin.class + ); + ResourceService resourceService = new ResourceService(resourceAccessControlPlugins); + final RestController restController = actionModule.getRestController(); final NodeResourceUsageTracker nodeResourceUsageTracker = new NodeResourceUsageTracker( @@ -1454,6 +1461,7 @@ protected Node( b.bind(ResourceUsageCollectorService.class).toInstance(resourceUsageCollectorService); b.bind(SystemIndices.class).toInstance(systemIndices); b.bind(IdentityService.class).toInstance(identityService); + b.bind(ResourceService.class).toInstance(resourceService); b.bind(Tracer.class).toInstance(tracer); b.bind(SearchRequestStats.class).toInstance(searchRequestStats); b.bind(SearchRequestSlowLog.class).toInstance(searchRequestSlowLog); diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java new file mode 100644 index 0000000000000..05fd6eb9aeddd --- /dev/null +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.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.plugins; + +/** + * Class to determine presence of security plugin in the cluster. + * If yes, security plugin will be used for resource access authorization + * + * @opensearch.experimental + */ +public interface ResourceAccessControlPlugin extends ResourcePlugin {} From 58ae851b40ff69ae4fbcd5a2d6bb85045af1d50c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 12:40:44 -0400 Subject: [PATCH 04/51] Fixes compilation errors and changes debug log-level to info for ResourceService Signed-off-by: Darshit Chanpura --- .../opensearch/accesscontrol/resources/ResourceService.java | 4 ++-- .../src/main/java/org/opensearch/plugins/ResourcePlugin.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index b95d693b07b72..cd4dbdb91a0df 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -27,10 +27,10 @@ public class ResourceService { public ResourceService(final List resourcePlugins) { if (resourcePlugins.size() == 0) { - log.debug("Security plugin disabled: Using NoopResourcePlugin"); + log.info("Security plugin disabled: Using NoOpResourcePlugin"); resourcePlugin = new NoOpResourcePlugin(); } else if (resourcePlugins.size() == 1) { - log.debug("Security plugin enabled: Using OpenSearchSecurityPlugin"); + log.info("Security plugin enabled: Using OpenSearchSecurityPlugin"); resourcePlugin = resourcePlugins.get(0); } else { throw new OpenSearchException( diff --git a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java index 3a357fd16c4a9..bd554c02d2dbb 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java @@ -18,7 +18,7 @@ * This plugin class defines usage mechanisms for plugins to interact with resources. * User information is fetched from thread context by security plugin. * In clusters, where security plugin is disabled these requests will be pass-through via a No-op implementation. - * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit {@linkplain this design proposal.} + * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit ... * If security plugin is disabled, all resources will be considered public by default. * TODO: add documentation around "how to use" * From fd002439af83fa9668e5df97257ec06d12f1857e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 12:41:46 -0400 Subject: [PATCH 05/51] Replace plugin count check with isEmpty Signed-off-by: Darshit Chanpura --- .../org/opensearch/accesscontrol/resources/ResourceService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index cd4dbdb91a0df..0786df5b03885 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -26,7 +26,7 @@ public class ResourceService { private final ResourcePlugin resourcePlugin; public ResourceService(final List resourcePlugins) { - if (resourcePlugins.size() == 0) { + if (resourcePlugins.isEmpty()) { log.info("Security plugin disabled: Using NoOpResourcePlugin"); resourcePlugin = new NoOpResourcePlugin(); } else if (resourcePlugins.size() == 1) { From ef8a0b70e4e787db3b2faeb45cd56637b594c59a Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 12:53:49 -0400 Subject: [PATCH 06/51] Adds package-info Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/package-info.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java new file mode 100644 index 0000000000000..65fe5c5f05713 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.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. + */ + +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Actions that OpenSearch can take either on the data stored on disk or on other nodes. + */ +/* + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/** + * This package defines all classes required for Resource Sharing and Access Control + */ +package org.opensearch.accesscontrol.resources; From e98cb61b3711392ef41bc9a737f4e791c93806dc Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 13:36:46 -0400 Subject: [PATCH 07/51] Renames a bunch of files Signed-off-by: Darshit Chanpura --- .../resources/ResourceService.java | 32 ++++++---- .../main/java/org/opensearch/node/Node.java | 4 +- ...a => NoOpResourceAccessControlPlugin.java} | 2 +- .../plugins/ResourceAccessControlPlugin.java | 58 +++++++++++++++++- .../opensearch/plugins/ResourcePlugin.java | 61 +------------------ 5 files changed, 83 insertions(+), 74 deletions(-) rename server/src/main/java/org/opensearch/plugins/{NoOpResourcePlugin.java => NoOpResourceAccessControlPlugin.java} (96%) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 0786df5b03885..4c403fed834ff 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -8,7 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; -import org.opensearch.plugins.NoOpResourcePlugin; +import org.opensearch.plugins.NoOpResourceAccessControlPlugin; import org.opensearch.plugins.ResourceAccessControlPlugin; import org.opensearch.plugins.ResourcePlugin; @@ -23,19 +23,22 @@ public class ResourceService { private static final Logger log = LogManager.getLogger(ResourceService.class); - private final ResourcePlugin resourcePlugin; + private final ResourceAccessControlPlugin resourceACPlugin; + private final List resourcePlugins; - public ResourceService(final List resourcePlugins) { - if (resourcePlugins.isEmpty()) { - log.info("Security plugin disabled: Using NoOpResourcePlugin"); - resourcePlugin = new NoOpResourcePlugin(); - } else if (resourcePlugins.size() == 1) { + public ResourceService(final List resourceACPlugins, List resourcePlugins) { + this.resourcePlugins = resourcePlugins; + + if (resourceACPlugins.isEmpty()) { + log.info("Security plugin disabled: Using NoOpResourceAccessControlPlugin"); + resourceACPlugin = new NoOpResourceAccessControlPlugin(); + } else if (resourceACPlugins.size() == 1) { log.info("Security plugin enabled: Using OpenSearchSecurityPlugin"); - resourcePlugin = resourcePlugins.get(0); + resourceACPlugin = resourceACPlugins.get(0); } else { throw new OpenSearchException( "Multiple resource access control plugins are not supported, found: " - + resourcePlugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(",")) + + resourceACPlugins.stream().map(Object::getClass).map(Class::getName).collect(Collectors.joining(",")) ); } } @@ -43,7 +46,14 @@ public ResourceService(final List resourcePlugins) /** * Gets the current ResourcePlugin to perform authorization */ - public ResourcePlugin getResourceAccessControlPlugin() { - return resourcePlugin; + public ResourceAccessControlPlugin getResourceAccessControlPlugin() { + return resourceACPlugin; + } + + /** + * List active plugins that define resources + */ + public List listResourcePlugins() { + return resourcePlugins; } } diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index 01373adbc28df..792a1b9af42f2 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -214,6 +214,7 @@ import org.opensearch.plugins.PluginsService; import org.opensearch.plugins.RepositoryPlugin; import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.ResourcePlugin; import org.opensearch.plugins.ScriptPlugin; import org.opensearch.plugins.SearchPipelinePlugin; import org.opensearch.plugins.SearchPlugin; @@ -1063,7 +1064,8 @@ protected Node( final List resourceAccessControlPlugins = pluginsService.filterPlugins( ResourceAccessControlPlugin.class ); - ResourceService resourceService = new ResourceService(resourceAccessControlPlugins); + final List resourcePlugins = pluginsService.filterPlugins(ResourcePlugin.class); + ResourceService resourceService = new ResourceService(resourceAccessControlPlugins, resourcePlugins); final RestController restController = actionModule.getRestController(); diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java similarity index 96% rename from server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java rename to server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index c0878a3855187..2d263b8c359ab 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourcePlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -19,7 +19,7 @@ * * @opensearch.experimental */ -public class NoOpResourcePlugin implements ResourcePlugin { +public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlugin { /** * Returns an empty list since security plugin is not defined. diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 05fd6eb9aeddd..02fbcb6a7563a 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -8,10 +8,64 @@ package org.opensearch.plugins; +import org.opensearch.accesscontrol.resources.Resource; +import org.opensearch.accesscontrol.resources.ShareWith; + +import java.util.List; +import java.util.Map; + /** - * Class to determine presence of security plugin in the cluster. + * This interface determines presence of security plugin in the cluster. * If yes, security plugin will be used for resource access authorization + * User information is fetched from thread context by security plugin. + * In clusters, where security plugin is disabled these requests will be pass-through via a No-op implementation. + * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit ... + * If security plugin is disabled, all resources will be considered public by default. + * TODO: add documentation around "how to use" * * @opensearch.experimental */ -public interface ResourceAccessControlPlugin extends ResourcePlugin {} +public interface ResourceAccessControlPlugin { + /** + * Returns all accessible resources for current user. + * + * @return list of {@link Resource} items accessible by current user. + */ + List listAccessibleResources(); + + /** + * Checks whether current user has permission to given resource. + * + * + * @param resource the resource on which access is to be checked + * @return true if current user has access, false otherwise + */ + boolean hasPermission(Resource resource); + + /** + * Adds an entity to the share-with. Resource needs to be in restricted mode. + * @param type One of the {@link ShareWith} types + * @param entities List of names with whom to share this resource with + * @return a message whether sharing was successful. + */ + String shareWith(ShareWith type, List entities); + + /** + * Revokes given permission to a resource + * + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined + * @param revokeAccess a map that contains entries of entities whose access should be revoked + * @return true if revoke was successful, false if there was a failure + */ + boolean revoke(String resourceId, String systemIndexName, Map> revokeAccess); + + /** + * Deletes an entry from .resource_sharing index + * @param resource The resource to be removed from the index + * @return true if resource record was deleted, false otherwise + */ + boolean deleteResourceSharingRecord(Resource resource); + + // TODO: Check whether methods for bulk updates are required +} diff --git a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java index bd554c02d2dbb..e6e75d68f8626 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java @@ -8,66 +8,9 @@ package org.opensearch.plugins; -import org.opensearch.accesscontrol.resources.Resource; -import org.opensearch.accesscontrol.resources.ShareWith; - -import java.util.List; -import java.util.Map; - /** - * This plugin class defines usage mechanisms for plugins to interact with resources. - * User information is fetched from thread context by security plugin. - * In clusters, where security plugin is disabled these requests will be pass-through via a No-op implementation. - * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit ... - * If security plugin is disabled, all resources will be considered public by default. - * TODO: add documentation around "how to use" - * - * + * This interface should be implemented by all the plugins that define one or more resources. * * @opensearch.experimental */ -public interface ResourcePlugin { - - /** - * Returns all accessible resources for current user. - * - * @return list of {@link Resource} items accessible by current user. - */ - List listAccessibleResources(); - - /** - * Checks whether current user has permission to given resource. - * - * - * @param resource the resource on which access is to be checked - * @return true if current user has access, false otherwise - */ - boolean hasPermission(Resource resource); - - /** - * Adds an entity to the share-with. Resource needs to be in restricted mode. - * @param type One of the {@link ShareWith} types - * @param entities List of names with whom to share this resource with - * @return a message whether sharing was successful. - */ - String shareWith(ShareWith type, List entities); - - /** - * Revokes given permission to a resource - * - * @param resourceId if of the resource to be updated - * @param systemIndexName index where this resource is defined - * @param revokeAccess a map that contains entries of entities whose access should be revoked - * @return true if revoke was successful, false if there was a failure - */ - boolean revoke(String resourceId, String systemIndexName, Map> revokeAccess); - - /** - * Deletes an entry from .resource_sharing index - * @param resource The resource to be removed from the index - * @return true if resource record was deleted, false otherwise - */ - boolean deleteResourceSharingRecord(Resource resource); - - // TODO: Check whether methods for bulk updates are required -} +public interface ResourcePlugin {} From 96f09b0a88e43c2901210acb8336a81b47f67f6f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 14:55:14 -0400 Subject: [PATCH 08/51] Changes method signatures to be inline with their usage Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/EntityType.java | 23 ++++ .../accesscontrol/resources/Resource.java | 101 ------------------ .../resources/ResourceSharing.java | 86 +++++++++++---- .../accesscontrol/resources/ShareWith.java | 48 ++++++++- .../NoOpResourceAccessControlPlugin.java | 45 +++++--- .../plugins/ResourceAccessControlPlugin.java | 38 ++++--- 6 files changed, 181 insertions(+), 160 deletions(-) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java delete mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java new file mode 100644 index 0000000000000..435172f4e5097 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java @@ -0,0 +1,23 @@ +/* + * 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.accesscontrol.resources; + +/** + * This enum contains the type of entities a resource can be shared with. + * + * @opensearch.experimental + */ +public enum EntityType { + + USERS, + + ROLES, + + BACKEND_ROLES, +} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java deleted file mode 100644 index 4212cb630cced..0000000000000 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java +++ /dev/null @@ -1,101 +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.accesscontrol.resources; - -import java.util.Objects; - -/** - * A document in .resource_sharing index. - * Holds information about the resource (obtained from defining plugin's meta-data), - * the index which defines the resources, the creator of the resource, - * and the information on whom this resource is shared with. - * - * @opensearch.experimental - */ -public class Resource { - - private String sourceIdx; - - private String resourceId; - - private CreatedBy createdBy; - - private ResourceSharing sharedWith; - - public Resource(String sourceIdx, String resourceId, CreatedBy createdBy, ResourceSharing sharedWith) { - this.sourceIdx = sourceIdx; - this.resourceId = resourceId; - this.createdBy = createdBy; - this.sharedWith = sharedWith; - } - - 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 ResourceSharing getSharedWith() { - return sharedWith; - } - - public void setSharedWith(ResourceSharing sharedWith) { - this.sharedWith = sharedWith; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Resource resource = (Resource) o; - return Objects.equals(getSourceIdx(), resource.getSourceIdx()) - && Objects.equals(getResourceId(), resource.getResourceId()) - && Objects.equals(getCreatedBy(), resource.getCreatedBy()) - && Objects.equals(getSharedWith(), resource.getSharedWith()); - } - - @Override - public int hashCode() { - return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getSharedWith()); - } - - @Override - public String toString() { - return "Resource {" - + "sourceIdx='" - + sourceIdx - + '\'' - + ", resourceId='" - + resourceId - + '\'' - + ", createdBy=" - + createdBy - + ", sharedWith=" - + sharedWith - + '}'; - } -} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java index 331ccb5de898c..7c866e0657a02 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java @@ -8,54 +8,94 @@ package org.opensearch.accesscontrol.resources; -import java.util.List; +import java.util.Objects; /** - * This class contains information about whom a resource is shared with. - * It could be a user-name, a role or a backend_role. + * A document in .resource_sharing index. + * Holds information about the resource (obtained from defining plugin's meta-data), + * the index which defines the resources, the creator of the resource, + * and the information on whom this resource is shared with. * * @opensearch.experimental */ public class ResourceSharing { - private List users; + private String sourceIdx; - private List roles; + private String resourceId; - private List backendRoles; + private CreatedBy createdBy; - public ResourceSharing(List users, List backendRoles, List roles) { - this.users = users; - this.backendRoles = backendRoles; - this.roles = roles; + private ShareWith sharedWith; + + public ResourceSharing(String sourceIdx, String resourceId, CreatedBy createdBy, ShareWith sharedWith) { + this.sourceIdx = sourceIdx; + this.resourceId = resourceId; + this.createdBy = createdBy; + this.sharedWith = sharedWith; + } + + public String getSourceIdx() { + return sourceIdx; } - public List getUsers() { - return users; + public void setSourceIdx(String sourceIdx) { + this.sourceIdx = sourceIdx; } - public void setUsers(List users) { - this.users = users; + public String getResourceId() { + return resourceId; } - public List getRoles() { - return roles; + public void setResourceId(String resourceId) { + this.resourceId = resourceId; } - public void setRoles(List roles) { - this.roles = roles; + public CreatedBy getCreatedBy() { + return createdBy; } - public List getBackendRoles() { - return backendRoles; + public void setCreatedBy(CreatedBy createdBy) { + this.createdBy = createdBy; } - public void setBackendRoles(List backendRoles) { - this.backendRoles = backendRoles; + public ShareWith getSharedWith() { + return sharedWith; + } + + public void setSharedWith(ShareWith sharedWith) { + this.sharedWith = sharedWith; + } + + @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(getSharedWith(), resourceSharing.getSharedWith()); + } + + @Override + public int hashCode() { + return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getSharedWith()); } @Override public String toString() { - return "ResourceSharing {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + return "Resource {" + + "sourceIdx='" + + sourceIdx + + '\'' + + ", resourceId='" + + resourceId + + '\'' + + ", createdBy=" + + createdBy + + ", sharedWith=" + + sharedWith + + '}'; } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index 702081ebd9ec9..980a4cd55b35e 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -8,16 +8,54 @@ package org.opensearch.accesscontrol.resources; +import java.util.List; + /** - * This enum contains the type of entities a resource can be shared with. + * This class contains information about whom a resource is shared with. + * It could be a user-name, a role or a backend_role. * * @opensearch.experimental */ -public enum ShareWith { +public class ShareWith { + + private List users; + + private List roles; + + private List backendRoles; + + public ShareWith(List users, List backendRoles, List roles) { + this.users = users; + this.backendRoles = backendRoles; + this.roles = roles; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } - USERS, + public List getBackendRoles() { + return backendRoles; + } - ROLES, + public void setBackendRoles(List backendRoles) { + this.backendRoles = backendRoles; + } - BACKEND_ROLES, + @Override + public String toString() { + return "ShareWith {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + } } diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index 2d263b8c359ab..743d3e248e1da 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -8,8 +8,8 @@ package org.opensearch.plugins; -import org.opensearch.accesscontrol.resources.Resource; -import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceSharing; import java.util.List; import java.util.Map; @@ -21,6 +21,15 @@ */ public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlugin { + /** + * + * @return an empty map of resource names accessible by this user. + */ + @Override + public Map> listAccessibleResources() { + return Map.of(); + } + /** * Returns an empty list since security plugin is not defined. * This method alone doesn't determine permissions. @@ -28,7 +37,7 @@ public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlu * @return empty list */ @Override - public List listAccessibleResources() { + public List listAccessibleResourcesForPlugin(String systemIndexName) { // returns an empty list since security plugin is disabled return List.of(); } @@ -36,23 +45,26 @@ public List listAccessibleResources() { /** * Returns true since no authorization is required. * - * @param resource the resource on which access is to be checked + * @param resourceId the resource on which access is to be checked + * @param systemIndexName where the resource exists * @return true */ - public boolean hasPermission(Resource resource) { + @Override + public boolean hasPermission(String resourceId, String systemIndexName) { return true; } /** * Adds an entity to the share-with. Resource needs to be in restricted mode. * - * @param type One of the {@link ShareWith} types - * @param entities List of names with whom to share this resource with - * @return a message whether sharing was successful. + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined + * @param revokeAccess a map that contains entries of entities with whom this resource should be shared with + * @return null since security plugin is disabled in the cluster */ - public String shareWith(ShareWith type, List entities) { - - return "Unable to share as security plugin is disabled in the cluster."; + @Override + public ResourceSharing shareWith(String resourceId, String systemIndexName, Map> revokeAccess) { + return null; } /** @@ -61,20 +73,21 @@ public String shareWith(ShareWith type, List entities) { * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @param revokeAccess a map that contains entries of entities whose access should be revoked - * @return false since no resource-sharing information is required as security plugin is disabled + * @return null since security plugin is disabled in the cluster */ @Override - public boolean revoke(String resourceId, String systemIndexName, Map> revokeAccess) { - return false; + public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { + return null; } /** * Delete a resource sharing record - * @param resource The resource to be removed from the index + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined * @return false since security plugin is disabled */ @Override - public boolean deleteResourceSharingRecord(Resource resource) { + public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { return false; } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 02fbcb6a7563a..e03f8e11aeccc 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -8,8 +8,8 @@ package org.opensearch.plugins; -import org.opensearch.accesscontrol.resources.Resource; -import org.opensearch.accesscontrol.resources.ShareWith; +import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.ResourceSharing; import java.util.List; import java.util.Map; @@ -28,27 +28,34 @@ public interface ResourceAccessControlPlugin { /** * Returns all accessible resources for current user. + * @return a map of resources accessible by user separated by system index names + */ + Map> listAccessibleResources(); + + /** + * Returns all accessible resources for current user for a given system . * - * @return list of {@link Resource} items accessible by current user. + * @return list of {@link ResourceSharing} items accessible by current user. */ - List listAccessibleResources(); + List listAccessibleResourcesForPlugin(String systemIndex); /** * Checks whether current user has permission to given resource. * - * - * @param resource the resource on which access is to be checked + * @param resourceId the resource on which access is to be checked + * @param systemIndexName where the resource exists * @return true if current user has access, false otherwise */ - boolean hasPermission(Resource resource); + boolean hasPermission(String resourceId, String systemIndexName); /** * Adds an entity to the share-with. Resource needs to be in restricted mode. - * @param type One of the {@link ShareWith} types - * @param entities List of names with whom to share this resource with - * @return a message whether sharing was successful. + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined + * @param entities a map that contains entries of entities with whom the resource should be shared with + * @return updated resource sharing record */ - String shareWith(ShareWith type, List entities); + ResourceSharing shareWith(String resourceId, String systemIndexName, Map> entities); /** * Revokes given permission to a resource @@ -56,16 +63,17 @@ public interface ResourceAccessControlPlugin { * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @param revokeAccess a map that contains entries of entities whose access should be revoked - * @return true if revoke was successful, false if there was a failure + * @return the updated ResourceSharing record */ - boolean revoke(String resourceId, String systemIndexName, Map> revokeAccess); + ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess); /** * Deletes an entry from .resource_sharing index - * @param resource The resource to be removed from the index + * @param resourceId if of the resource to be updated + * @param systemIndexName index where this resource is defined * @return true if resource record was deleted, false otherwise */ - boolean deleteResourceSharingRecord(Resource resource); + boolean deleteResourceSharingRecord(String resourceId, String systemIndexName); // TODO: Check whether methods for bulk updates are required } From c86dfc98ccd868feb2ed09dd5470bae18d825bda Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 30 Aug 2024 15:03:12 -0400 Subject: [PATCH 09/51] Adds new method for deleting by entity Signed-off-by: Darshit Chanpura --- .../plugins/NoOpResourceAccessControlPlugin.java | 16 +++++++++------- .../plugins/ResourceAccessControlPlugin.java | 7 +++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index 743d3e248e1da..af6e79e34f84c 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -43,8 +43,6 @@ public List listAccessibleResourcesForPlugin(String systemIndexName) { } /** - * Returns true since no authorization is required. - * * @param resourceId the resource on which access is to be checked * @param systemIndexName where the resource exists * @return true @@ -55,8 +53,6 @@ public boolean hasPermission(String resourceId, String systemIndexName) { } /** - * Adds an entity to the share-with. Resource needs to be in restricted mode. - * * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @param revokeAccess a map that contains entries of entities with whom this resource should be shared with @@ -68,8 +64,6 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Map< } /** - * Revokes access to the resource - * * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @param revokeAccess a map that contains entries of entities whose access should be revoked @@ -81,7 +75,6 @@ public ResourceSharing revokeAccess(String resourceId, String systemIndexName, M } /** - * Delete a resource sharing record * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @return false since security plugin is disabled @@ -91,4 +84,13 @@ public boolean deleteResourceSharingRecord(String resourceId, String systemIndex return false; } + /** + * @param entity whose resource sharing entries are to be deleted + * @return false since security plugin is disabled + */ + @Override + public boolean deleteAllResourceSharingRecordsFor(String entity) { + return false; + } + } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index e03f8e11aeccc..6bcd3150b59db 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -75,5 +75,12 @@ public interface ResourceAccessControlPlugin { */ boolean deleteResourceSharingRecord(String resourceId, String systemIndexName); + /** + * Deletes all entries from .resource_sharing index where requested entity is the creator of the resource + * @param entity whose resource sharing records are to be deleted + * @return true if resource record was deleted, false otherwise + */ + boolean deleteAllResourceSharingRecordsFor(String entity); + // TODO: Check whether methods for bulk updates are required } From 7c6ec2a670ea7fa27aa8be19b61ab43ff9f5d3c2 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 3 Sep 2024 11:09:03 -0400 Subject: [PATCH 10/51] Adds abstract method definitions for ResourcePlugin interface Signed-off-by: Darshit Chanpura --- .../org/opensearch/plugins/ResourcePlugin.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java index e6e75d68f8626..4fbbbf35fc367 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourcePlugin.java @@ -13,4 +13,17 @@ * * @opensearch.experimental */ -public interface ResourcePlugin {} +public interface ResourcePlugin { + + /** + * 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(); +} From f95a67f77d12b0cd4815dcd23bc25998c7a50618 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 6 Sep 2024 12:29:54 -0400 Subject: [PATCH 11/51] Adds toXContent implementations Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/CreatedBy.java | 12 ++++++- .../resources/ResourceSharing.java | 36 +++++++++++++------ .../accesscontrol/resources/ShareWith.java | 15 ++++++-- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java index 01c61e82ed5f4..d5c521e6c20f3 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java @@ -8,13 +8,18 @@ package org.opensearch.accesscontrol.resources; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; + /** * This class contains information on the creator of a resource. * Creator can either be a user or a backend_role. * * @opensearch.experimental */ -public class CreatedBy { +public class CreatedBy implements ToXContentFragment { private String user; @@ -45,4 +50,9 @@ public void setUser(String user) { public String toString() { return "CreatedBy {" + "user='" + user + '\'' + ", backendRole='" + backendRole + '\'' + '}'; } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("user", user).field("backend_role", backendRole).endObject(); + } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java index 7c866e0657a02..bb5cdfb78a41e 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java @@ -8,6 +8,10 @@ package org.opensearch.accesscontrol.resources; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; import java.util.Objects; /** @@ -18,7 +22,7 @@ * * @opensearch.experimental */ -public class ResourceSharing { +public class ResourceSharing implements ToXContentFragment { private String sourceIdx; @@ -26,13 +30,13 @@ public class ResourceSharing { private CreatedBy createdBy; - private ShareWith sharedWith; + private ShareWith shareWith; - public ResourceSharing(String sourceIdx, String resourceId, CreatedBy createdBy, ShareWith sharedWith) { + public ResourceSharing(String sourceIdx, String resourceId, CreatedBy createdBy, ShareWith shareWith) { this.sourceIdx = sourceIdx; this.resourceId = resourceId; this.createdBy = createdBy; - this.sharedWith = sharedWith; + this.shareWith = shareWith; } public String getSourceIdx() { @@ -59,12 +63,12 @@ public void setCreatedBy(CreatedBy createdBy) { this.createdBy = createdBy; } - public ShareWith getSharedWith() { - return sharedWith; + public ShareWith getShareWith() { + return shareWith; } - public void setSharedWith(ShareWith sharedWith) { - this.sharedWith = sharedWith; + public void setShareWith(ShareWith shareWith) { + this.shareWith = shareWith; } @Override @@ -75,12 +79,12 @@ public boolean equals(Object o) { return Objects.equals(getSourceIdx(), resourceSharing.getSourceIdx()) && Objects.equals(getResourceId(), resourceSharing.getResourceId()) && Objects.equals(getCreatedBy(), resourceSharing.getCreatedBy()) - && Objects.equals(getSharedWith(), resourceSharing.getSharedWith()); + && Objects.equals(getShareWith(), resourceSharing.getShareWith()); } @Override public int hashCode() { - return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getSharedWith()); + return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getShareWith()); } @Override @@ -95,7 +99,17 @@ public String toString() { + ", createdBy=" + createdBy + ", sharedWith=" - + sharedWith + + shareWith + '}'; } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject() + .field("source_idx", sourceIdx) + .field("resource_id", resourceId) + .field("created_by", createdBy) + .field("share_with", shareWith) + .endObject(); + } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index 980a4cd55b35e..8bb3730bacd19 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -8,6 +8,10 @@ package org.opensearch.accesscontrol.resources; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; import java.util.List; /** @@ -16,7 +20,7 @@ * * @opensearch.experimental */ -public class ShareWith { +public class ShareWith implements ToXContentFragment { private List users; @@ -24,10 +28,10 @@ public class ShareWith { private List backendRoles; - public ShareWith(List users, List backendRoles, List roles) { + public ShareWith(List users, List roles, List backendRoles) { this.users = users; - this.backendRoles = backendRoles; this.roles = roles; + this.backendRoles = backendRoles; } public List getUsers() { @@ -58,4 +62,9 @@ public void setBackendRoles(List backendRoles) { public String toString() { return "ShareWith {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("users", users).field("roles", roles).field("backend_roles", backendRoles).endObject(); + } } From 7e7cd0ad7bd0da264cc235cb8c379a5d9864ecbd Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 10 Sep 2024 12:50:32 -0400 Subject: [PATCH 12/51] Modifies some method names and comments Signed-off-by: Darshit Chanpura --- .../plugins/NoOpResourceAccessControlPlugin.java | 8 ++++---- .../plugins/ResourceAccessControlPlugin.java | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index af6e79e34f84c..c3fae97380186 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -10,6 +10,7 @@ import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; import java.util.List; import java.util.Map; @@ -55,11 +56,11 @@ public boolean hasPermission(String resourceId, String systemIndexName) { /** * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined - * @param revokeAccess a map that contains entries of entities with whom this resource should be shared with + * @param shareWith a map that contains entries of entities with whom this resource should be shared with * @return null since security plugin is disabled in the cluster */ @Override - public ResourceSharing shareWith(String resourceId, String systemIndexName, Map> revokeAccess) { + public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { return null; } @@ -85,11 +86,10 @@ public boolean deleteResourceSharingRecord(String resourceId, String systemIndex } /** - * @param entity whose resource sharing entries are to be deleted * @return false since security plugin is disabled */ @Override - public boolean deleteAllResourceSharingRecordsFor(String entity) { + public boolean deleteAllResourceSharingRecordsForCurrentUser() { return false; } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 6bcd3150b59db..f23fdefda0230 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -10,6 +10,7 @@ import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; +import org.opensearch.accesscontrol.resources.ShareWith; import java.util.List; import java.util.Map; @@ -50,12 +51,13 @@ public interface ResourceAccessControlPlugin { /** * Adds an entity to the share-with. Resource needs to be in restricted mode. - * @param resourceId if of the resource to be updated + * Creates a resource sharing record if one doesn't exist. + * @param resourceId id of the resource to be updated * @param systemIndexName index where this resource is defined - * @param entities a map that contains entries of entities with whom the resource should be shared with + * @param shareWith an object that contains entries of entities with whom the resource should be shared with * @return updated resource sharing record */ - ResourceSharing shareWith(String resourceId, String systemIndexName, Map> entities); + ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith); /** * Revokes given permission to a resource @@ -76,11 +78,11 @@ public interface ResourceAccessControlPlugin { boolean deleteResourceSharingRecord(String resourceId, String systemIndexName); /** - * Deletes all entries from .resource_sharing index where requested entity is the creator of the resource - * @param entity whose resource sharing records are to be deleted + * TODO check if this method is needed + * Deletes all entries from .resource_sharing index where current user is the creator of the resource * @return true if resource record was deleted, false otherwise */ - boolean deleteAllResourceSharingRecordsFor(String entity); + boolean deleteAllResourceSharingRecordsForCurrentUser(); // TODO: Check whether methods for bulk updates are required } From 23fcfba74ab701ff9b544f441eb54d59bf61e036 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 2 Oct 2024 16:41:28 -0400 Subject: [PATCH 13/51] Fixes license Signed-off-by: Darshit Chanpura --- .../resources/ResourceService.java | 5 +++- .../accesscontrol/resources/package-info.java | 27 ------------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 4c403fed834ff..1a5fd9c8d8701 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -1,6 +1,9 @@ /* - * 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.accesscontrol.resources; diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java index 65fe5c5f05713..b8ad5237a60ca 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java @@ -6,33 +6,6 @@ * compatible open source license. */ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Actions that OpenSearch can take either on the data stored on disk or on other nodes. - */ -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - /** * This package defines all classes required for Resource Sharing and Access Control */ From fba48abd7e0353e96b9b4f2247618d978bf3c37f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 2 Oct 2024 16:44:14 -0400 Subject: [PATCH 14/51] Adds changelog entry Signed-off-by: Darshit Chanpura --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 333b1cd19b404..cf4c76e0276f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add success and failure metrics for async shard fetch ([#15976](https://github.com/opensearch-project/OpenSearch/pull/15976)) - Add new metric REMOTE_STORE to NodeStats API response ([#15611](https://github.com/opensearch-project/OpenSearch/pull/15611)) - [S3 Repository] Change default retry mechanism of s3 clients to Standard Mode ([#15978](https://github.com/opensearch-project/OpenSearch/pull/15978)) +- Add resource-level access control and sharing ([#16030](https://github.com/opensearch-project/OpenSearch/pull/16030)) ### Dependencies - Bump `com.azure:azure-identity` from 1.13.0 to 1.13.2 ([#15578](https://github.com/opensearch-project/OpenSearch/pull/15578)) From 9cb8d0e9b653f077101e9e0a9e61b5db5c329d64 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 2 Oct 2024 17:11:26 -0400 Subject: [PATCH 15/51] Adds a notion of scope Signed-off-by: Darshit Chanpura --- .../resources/ResourceAccessScope.java | 21 +++++++++++++++++++ .../NoOpResourceAccessControlPlugin.java | 3 ++- .../plugins/ResourceAccessControlPlugin.java | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java new file mode 100644 index 0000000000000..463936625522c --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.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.accesscontrol.resources; + +/** + * 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"; +} diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index c3fae97380186..2619b05124b67 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -46,10 +46,11 @@ public List listAccessibleResourcesForPlugin(String systemIndexName) { /** * @param resourceId the resource on which access is to be checked * @param systemIndexName where the resource exists + * @param scope the type of access being requested * @return true */ @Override - public boolean hasPermission(String resourceId, String systemIndexName) { + public boolean hasPermission(String resourceId, String systemIndexName, String scope) { return true; } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index f23fdefda0230..8298a65becfd7 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -45,9 +45,10 @@ public interface ResourceAccessControlPlugin { * * @param resourceId the resource on which access is to be checked * @param systemIndexName where the resource exists + * @param scope the scope being requested * @return true if current user has access, false otherwise */ - boolean hasPermission(String resourceId, String systemIndexName); + boolean hasPermission(String resourceId, String systemIndexName, String scope); /** * Adds an entity to the share-with. Resource needs to be in restricted mode. From 848234e16ab1ffc8a8a71b15f0e60a8512e04e95 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 12:56:53 -0400 Subject: [PATCH 16/51] Modifies sharedwith to accomodate scope Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/ShareWith.java | 59 ++++------- .../resources/SharedWithScope.java | 98 +++++++++++++++++++ 2 files changed, 118 insertions(+), 39 deletions(-) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index 8bb3730bacd19..df63e8fa8f02d 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -15,56 +15,37 @@ import java.util.List; /** - * This class contains information about whom a resource is shared with. - * It could be a user-name, a role or a backend_role. + * This class contains information about whom a resource is shared with and at what scope. + * Here is a sample of what this would look like: + * "share_with": { + * "read_only": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * }, + * "read_write": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * } + * } * * @opensearch.experimental */ public class ShareWith implements ToXContentFragment { - private List users; + private final List sharedWithScopes; - private List roles; - - private List backendRoles; - - public ShareWith(List users, List roles, List backendRoles) { - this.users = users; - this.roles = roles; - this.backendRoles = backendRoles; - } - - public List getUsers() { - return users; - } - - public void setUsers(List users) { - this.users = users; - } - - public List getRoles() { - return roles; + public ShareWith(List sharedWithScopes) { + this.sharedWithScopes = sharedWithScopes; } - public void setRoles(List roles) { - this.roles = roles; - } - - public List getBackendRoles() { - return backendRoles; - } - - public void setBackendRoles(List backendRoles) { - this.backendRoles = backendRoles; - } - - @Override - public String toString() { - return "ShareWith {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + public List getSharedWithScopes() { + return sharedWithScopes; } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("users", users).field("roles", roles).field("backend_roles", backendRoles).endObject(); + return builder.startObject("share_with").value(sharedWithScopes).endObject(); } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java new file mode 100644 index 0000000000000..2bc1f4614e227 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -0,0 +1,98 @@ +/* + * 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.accesscontrol.resources; + +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.List; + +/** + * Defines the scope and who this scope is shared with + * + * @opensearch.experimental + */ +public class SharedWithScope implements ToXContentFragment { + + private final String scope; + + private final SharedWithPerScope sharedWithPerScope; + + public SharedWithScope(String scope, SharedWithPerScope sharedWithPerScope) { + this.scope = scope; + this.sharedWithPerScope = sharedWithPerScope; + } + + public String getScope() { + return scope; + } + + public SharedWithPerScope getSharedWithPerScope() { + return sharedWithPerScope; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field(scope, sharedWithPerScope).endObject(); + } + + @Override + public String toString() { + return "SharedWithScope {" + scope + ": " + sharedWithPerScope + '}'; + } + + public static class SharedWithPerScope implements ToXContentFragment { + private List users; + + private List roles; + + private List backendRoles; + + public SharedWithPerScope(List users, List roles, List backendRoles) { + this.users = users; + this.roles = roles; + this.backendRoles = backendRoles; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getBackendRoles() { + return backendRoles; + } + + public void setBackendRoles(List backendRoles) { + this.backendRoles = backendRoles; + } + + @Override + public String toString() { + return "ShareWith {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("users", users).field("roles", roles).field("backend_roles", backendRoles).endObject(); + } + } +} From eaf0c6ea96075508a71c8793bcf6c16ad4468f7a Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 13:27:04 -0400 Subject: [PATCH 17/51] Adds missing JavaDoc Signed-off-by: Darshit Chanpura --- .../opensearch/accesscontrol/resources/SharedWithScope.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index 2bc1f4614e227..744419ad4a7be 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -48,6 +48,11 @@ public String toString() { return "SharedWithScope {" + scope + ": " + sharedWithPerScope + '}'; } + /** + * This class defines who a resource is shared_with for a particular scope + * + * @opensearch.experimental + */ public static class SharedWithPerScope implements ToXContentFragment { private List users; From 566913ae6ff7009d3c3bda9a2464934263f3f80e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 4 Oct 2024 18:14:15 -0400 Subject: [PATCH 18/51] Adds NamedWriteable capability and removes un-needed method Signed-off-by: Darshit Chanpura --- .../resources/ResourceService.java | 2 +- .../accesscontrol/resources/ShareWith.java | 14 ++++++++- .../resources/SharedWithScope.java | 29 +++++++++++++++++-- .../NoOpResourceAccessControlPlugin.java | 11 +------ .../plugins/ResourceAccessControlPlugin.java | 7 +---- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 1a5fd9c8d8701..41ccd4739d7ec 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -56,7 +56,7 @@ public ResourceAccessControlPlugin getResourceAccessControlPlugin() { /** * List active plugins that define resources */ - public List listResourcePlugins() { + List listResourcePlugins() { return resourcePlugins; } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index df63e8fa8f02d..2018cdfdfd96d 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -8,6 +8,8 @@ package org.opensearch.accesscontrol.resources; +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; @@ -32,7 +34,7 @@ * * @opensearch.experimental */ -public class ShareWith implements ToXContentFragment { +public class ShareWith implements ToXContentFragment, NamedWriteable { private final List sharedWithScopes; @@ -48,4 +50,14 @@ public List getSharedWithScopes() { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.startObject("share_with").value(sharedWithScopes).endObject(); } + + @Override + public String getWriteableName() { + return "share_with"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeList(sharedWithScopes); + } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index 744419ad4a7be..1f424177fcd4e 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -8,6 +8,8 @@ package org.opensearch.accesscontrol.resources; +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; @@ -19,7 +21,7 @@ * * @opensearch.experimental */ -public class SharedWithScope implements ToXContentFragment { +public class SharedWithScope implements ToXContentFragment, NamedWriteable { private final String scope; @@ -48,12 +50,23 @@ public String toString() { return "SharedWithScope {" + scope + ": " + sharedWithPerScope + '}'; } + @Override + public String getWriteableName() { + return "shared_with_scope"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(scope); + out.writeNamedWriteable(sharedWithPerScope); + } + /** * This class defines who a resource is shared_with for a particular scope * * @opensearch.experimental */ - public static class SharedWithPerScope implements ToXContentFragment { + public static class SharedWithPerScope implements ToXContentFragment, NamedWriteable { private List users; private List roles; @@ -99,5 +112,17 @@ public String toString() { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return builder.startObject().field("users", users).field("roles", roles).field("backend_roles", backendRoles).endObject(); } + + @Override + public String getWriteableName() { + return "shared_with_per_scope"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringArray(users.toArray(new String[0])); + out.writeStringArray(roles.toArray(new String[0])); + out.writeStringArray(backendRoles.toArray(new String[0])); + } } } diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index 2619b05124b67..ad67ecc6e9ef1 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -22,15 +22,6 @@ */ public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlugin { - /** - * - * @return an empty map of resource names accessible by this user. - */ - @Override - public Map> listAccessibleResources() { - return Map.of(); - } - /** * Returns an empty list since security plugin is not defined. * This method alone doesn't determine permissions. @@ -38,7 +29,7 @@ public Map> listAccessibleResources() { * @return empty list */ @Override - public List listAccessibleResourcesForPlugin(String systemIndexName) { + public List listAccessibleResourcesInPlugin(String systemIndexName) { // returns an empty list since security plugin is disabled return List.of(); } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 8298a65becfd7..379fee4be090b 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -27,18 +27,13 @@ * @opensearch.experimental */ public interface ResourceAccessControlPlugin { - /** - * Returns all accessible resources for current user. - * @return a map of resources accessible by user separated by system index names - */ - Map> listAccessibleResources(); /** * Returns all accessible resources for current user for a given system . * * @return list of {@link ResourceSharing} items accessible by current user. */ - List listAccessibleResourcesForPlugin(String systemIndex); + List listAccessibleResourcesInPlugin(String systemIndex); /** * Checks whether current user has permission to given resource. From 9baac3237590e8ccda90788784b904f81492a41c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 10 Oct 2024 19:14:03 -0400 Subject: [PATCH 19/51] Updates toXContent implementations Signed-off-by: Darshit Chanpura --- .../resources/ResourceSharing.java | 13 ++++++------ .../accesscontrol/resources/ShareWith.java | 8 +++++++- .../resources/SharedWithScope.java | 20 +++++++++++++++++-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java index bb5cdfb78a41e..f594bc790953b 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java @@ -105,11 +105,12 @@ public String toString() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject() - .field("source_idx", sourceIdx) - .field("resource_id", resourceId) - .field("created_by", createdBy) - .field("share_with", shareWith) - .endObject(); + 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(); } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index 2018cdfdfd96d..44ac1fdcdeedc 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -48,7 +48,13 @@ public List getSharedWithScopes() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject("share_with").value(sharedWithScopes).endObject(); + builder.startObject(); + + for (SharedWithScope scope : sharedWithScopes) { + scope.toXContent(builder, params); + } + + return builder.endObject(); } @Override diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index 1f424177fcd4e..06dec85feb95a 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -42,7 +42,12 @@ public SharedWithPerScope getSharedWithPerScope() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field(scope, sharedWithPerScope).endObject(); + builder.field(scope); + builder.startObject(); + + sharedWithPerScope.toXContent(builder, params); + + return builder.endObject(); } @Override @@ -110,7 +115,18 @@ public String toString() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("users", users).field("roles", roles).field("backend_roles", backendRoles).endObject(); + writeFieldOrEmptyArray(builder, "users", users); + writeFieldOrEmptyArray(builder, "roles", roles); + writeFieldOrEmptyArray(builder, "backend_roles", backendRoles); + return builder; + } + + private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, List values) throws IOException { + if (values != null) { + builder.field(fieldName, values); + } else { + builder.array(fieldName); + } } @Override From 0eb47ace05e92db70c5f09e1415318da49e2eaba Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 10 Oct 2024 19:27:23 -0400 Subject: [PATCH 20/51] Fix toString implementation Signed-off-by: Darshit Chanpura --- .../org/opensearch/accesscontrol/resources/ShareWith.java | 5 +++++ .../opensearch/accesscontrol/resources/SharedWithScope.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index 44ac1fdcdeedc..e996933494dde 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -66,4 +66,9 @@ public String getWriteableName() { public void writeTo(StreamOutput out) throws IOException { out.writeList(sharedWithScopes); } + + @Override + public String toString() { + return "ShareWith " + sharedWithScopes; + } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index 06dec85feb95a..a471dc5300e58 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -52,7 +52,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws @Override public String toString() { - return "SharedWithScope {" + scope + ": " + sharedWithPerScope + '}'; + return "{" + scope + ": " + sharedWithPerScope + '}'; } @Override @@ -110,7 +110,7 @@ public void setBackendRoles(List backendRoles) { @Override public String toString() { - return "ShareWith {" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + return "{" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; } @Override From e313071c01bf6c1c406e794d7a2435a0ab90bca0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 15 Oct 2024 01:09:01 -0400 Subject: [PATCH 21/51] Allows the ability to list resource permissions Signed-off-by: Darshit Chanpura --- .../org/opensearch/accesscontrol/resources/ResourceService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 41ccd4739d7ec..1a5fd9c8d8701 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -56,7 +56,7 @@ public ResourceAccessControlPlugin getResourceAccessControlPlugin() { /** * List active plugins that define resources */ - List listResourcePlugins() { + public List listResourcePlugins() { return resourcePlugins; } } From 37cacf05cea1bd2270f6f102384e98e687e6c19d Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 27 Nov 2024 14:19:03 -0500 Subject: [PATCH 22/51] Adds NamedWriteable implementations Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/CreatedBy.java | 56 ++++++++++--- .../accesscontrol/resources/EntityType.java | 15 +++- .../resources/ResourceSharing.java | 71 +++++++++++++++- .../accesscontrol/resources/ShareWith.java | 27 +++++++ .../resources/SharedWithScope.java | 81 ++++++++++++++++++- .../common/util/concurrent/ThreadContext.java | 5 +- 6 files changed, 232 insertions(+), 23 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java index d5c521e6c20f3..8482ff4c1318f 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java @@ -8,8 +8,12 @@ package org.opensearch.accesscontrol.resources; +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; import java.io.IOException; @@ -19,23 +23,16 @@ * * @opensearch.experimental */ -public class CreatedBy implements ToXContentFragment { +public class CreatedBy implements ToXContentFragment, NamedWriteable { private String user; - private String backendRole; - - public CreatedBy(String user, String backendRole) { + public CreatedBy(String user) { this.user = user; - this.backendRole = backendRole; - } - - public String getBackendRole() { - return backendRole; } - public void setBackendRole(String backendRole) { - this.backendRole = backendRole; + public CreatedBy(StreamInput in) throws IOException { + this(in.readString()); } public String getUser() { @@ -48,11 +45,44 @@ public void setUser(String user) { @Override public String toString() { - return "CreatedBy {" + "user='" + user + '\'' + ", backendRole='" + backendRole + '\'' + '}'; + return "CreatedBy {" + "user='" + user + '\'' + '}'; + } + + @Override + public String getWriteableName() { + return "created_by"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(user); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("user", user).field("backend_role", backendRole).endObject(); + return builder.startObject().field("user", user).endObject(); } + + public static CreatedBy fromXContent(XContentParser parser) throws IOException { + String user = 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 if (token == XContentParser.Token.VALUE_STRING) { + if ("user".equals(currentFieldName)) { + user = parser.text(); + } + } + } + + if (user == null) { + throw new IllegalArgumentException("user field is required"); + } + + return new CreatedBy(user); + } + } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java index 435172f4e5097..e82f4a52c9381 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java @@ -15,9 +15,18 @@ */ public enum EntityType { - USERS, + USERS("users"), + ROLES("roles"), + BACKEND_ROLES("backend_roles"); - ROLES, + private final String value; - BACKEND_ROLES, + EntityType(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java index f594bc790953b..421d3a0366903 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java @@ -8,8 +8,11 @@ package org.opensearch.accesscontrol.resources; +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; import java.io.IOException; import java.util.Objects; @@ -22,7 +25,7 @@ * * @opensearch.experimental */ -public class ResourceSharing implements ToXContentFragment { +public class ResourceSharing implements ToXContentFragment, NamedWriteable { private String sourceIdx; @@ -103,6 +106,24 @@ public String toString() { + '}'; } + @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"); @@ -113,4 +134,52 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } 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; + } + } + } + + // Validate required fields + if (sourceIdx == null) { + throw new IllegalArgumentException("source_idx is required"); + } + if (resourceId == null) { + throw new IllegalArgumentException("resource_id is required"); + } + if (createdBy == null) { + throw new IllegalArgumentException("created_by is required"); + } + + return new ResourceSharing(sourceIdx, resourceId, createdBy, shareWith); + } + } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index e996933494dde..d474d7cb762da 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -9,11 +9,14 @@ package org.opensearch.accesscontrol.resources; 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; import java.io.IOException; +import java.util.ArrayList; import java.util.List; /** @@ -42,6 +45,10 @@ public ShareWith(List sharedWithScopes) { this.sharedWithScopes = sharedWithScopes; } + public ShareWith(StreamInput in) throws IOException { + this.sharedWithScopes = in.readList(SharedWithScope::new); + } + public List getSharedWithScopes() { return sharedWithScopes; } @@ -57,6 +64,26 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder.endObject(); } + public static ShareWith fromXContent(XContentParser parser) throws IOException { + List sharedWithScopes = new ArrayList<>(); + + // Ensure we're at the start of the object + 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"; diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index a471dc5300e58..a3c70fdeabfbc 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -9,11 +9,15 @@ package org.opensearch.accesscontrol.resources; 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; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -32,6 +36,11 @@ public SharedWithScope(String scope, SharedWithPerScope sharedWithPerScope) { this.sharedWithPerScope = sharedWithPerScope; } + public SharedWithScope(StreamInput in) throws IOException { + this.scope = in.readString(); + this.sharedWithPerScope = new SharedWithPerScope(in); + } + public String getScope() { return scope; } @@ -50,6 +59,16 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder.endObject(); } + public static SharedWithScope fromXContent(XContentParser parser) throws IOException { + String scope = parser.currentName(); + + parser.nextToken(); + + SharedWithPerScope sharedWithPerScope = SharedWithPerScope.fromXContent(parser); + + return new SharedWithScope(scope, sharedWithPerScope); + } + @Override public String toString() { return "{" + scope + ": " + sharedWithPerScope + '}'; @@ -72,6 +91,10 @@ public void writeTo(StreamOutput out) throws IOException { * @opensearch.experimental */ public static class SharedWithPerScope implements ToXContentFragment, NamedWriteable { + private static final String USERS_FIELD = EntityType.USERS.toString(); + private static final String ROLES_FIELD = EntityType.ROLES.toString(); + private static final String BACKEND_ROLES_FIELD = EntityType.BACKEND_ROLES.toString(); + private List users; private List roles; @@ -84,6 +107,12 @@ public SharedWithPerScope(List users, List roles, List b this.backendRoles = backendRoles; } + public SharedWithPerScope(StreamInput in) throws IOException { + this.users = Arrays.asList(in.readStringArray()); + this.roles = Arrays.asList(in.readStringArray()); + this.backendRoles = Arrays.asList(in.readStringArray()); + } + public List getUsers() { return users; } @@ -110,17 +139,61 @@ public void setBackendRoles(List backendRoles) { @Override public String toString() { - return "{" + "users=" + users + ", roles=" + roles + ", backendRoles=" + backendRoles + '}'; + return "{" + + USERS_FIELD + + "=" + + users + + ", " + + ROLES_FIELD + + "=" + + roles + + ", " + + BACKEND_ROLES_FIELD + + "=" + + backendRoles + + '}'; } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - writeFieldOrEmptyArray(builder, "users", users); - writeFieldOrEmptyArray(builder, "roles", roles); - writeFieldOrEmptyArray(builder, "backend_roles", backendRoles); + writeFieldOrEmptyArray(builder, USERS_FIELD, users); + writeFieldOrEmptyArray(builder, ROLES_FIELD, roles); + writeFieldOrEmptyArray(builder, BACKEND_ROLES_FIELD, backendRoles); return builder; } + public static SharedWithPerScope fromXContent(XContentParser parser) throws IOException { + List users = new ArrayList<>(); + List roles = new ArrayList<>(); + List backendRoles = new ArrayList<>(); + + XContentParser.Token token; + String currentFieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.START_ARRAY) { + if (USERS_FIELD.equals(currentFieldName)) { + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + users.add(parser.text()); + } + } else if (ROLES_FIELD.equals(currentFieldName)) { + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + roles.add(parser.text()); + } + } else if (BACKEND_ROLES_FIELD.equals(currentFieldName)) { + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + backendRoles.add(parser.text()); + } + } else { + parser.skipChildren(); + } + } + } + + return new SharedWithPerScope(users, roles, backendRoles); + } + private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, List values) throws IOException { if (values != null) { builder.field(fieldName, values); diff --git a/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java b/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java index 75a7ef94978d4..081e2afe936ef 100644 --- a/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java +++ b/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java @@ -411,8 +411,9 @@ public String getHeader(String key) { /** * Returns the persistent header for the given key or null if not present - persistent headers cannot be stashed */ - public Object getPersistent(String key) { - return threadLocal.get().persistentHeaders.get(key); + @SuppressWarnings("unchecked") // (T)object + public T getPersistent(String key) { + return (T) threadLocal.get().persistentHeaders.get(key); } /** From 410740740def8cab12b8f955fe3cb92cd878e5d0 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 3 Dec 2024 17:34:04 -0500 Subject: [PATCH 23/51] Adds TODO for Noop Implementation Signed-off-by: Darshit Chanpura --- .../java/org/opensearch/accesscontrol/resources/ShareWith.java | 1 - .../org/opensearch/plugins/NoOpResourceAccessControlPlugin.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index d474d7cb762da..1761efb45af3e 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -67,7 +67,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws public static ShareWith fromXContent(XContentParser parser) throws IOException { List sharedWithScopes = new ArrayList<>(); - // Ensure we're at the start of the object if (parser.currentToken() != XContentParser.Token.START_OBJECT) { parser.nextToken(); } diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index ad67ecc6e9ef1..07c3e45c6f89a 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -31,6 +31,7 @@ public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlu @Override public List listAccessibleResourcesInPlugin(String systemIndexName) { // returns an empty list since security plugin is disabled + // TODO: check whether this should return all entries in the given index return List.of(); } From 274c64f96f2ce6406e6073893b4b541228461709 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 4 Dec 2024 12:30:45 -0500 Subject: [PATCH 24/51] Adds fromValue method to EntityType Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/EntityType.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java index e82f4a52c9381..070585562e4e6 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java @@ -8,6 +8,11 @@ package org.opensearch.accesscontrol.resources; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + /** * This enum contains the type of entities a resource can be shared with. * @@ -19,6 +24,9 @@ public enum EntityType { ROLES("roles"), BACKEND_ROLES("backend_roles"); + private static final Map VALUE_MAP = Arrays.stream(values()) + .collect(Collectors.toMap(EntityType::toString, Function.identity())); + private final String value; EntityType(String value) { @@ -29,4 +37,12 @@ public enum EntityType { public String toString() { return value; } + + public static EntityType fromValue(String value) { + EntityType type = VALUE_MAP.get(value); + if (type == null) { + throw new IllegalArgumentException("No enum constant with value: " + value); + } + return type; + } } From 014be82ac82c6699bc49cd8e92c499a92e9048e9 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 12:23:05 -0500 Subject: [PATCH 25/51] Refactors variables from List to Set Signed-off-by: Darshit Chanpura --- .../resources/SharedWithScope.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index a3c70fdeabfbc..dae66785269a4 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -16,9 +16,8 @@ import org.opensearch.core.xcontent.XContentParser; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.HashSet; +import java.util.Set; /** * Defines the scope and who this scope is shared with @@ -95,45 +94,45 @@ public static class SharedWithPerScope implements ToXContentFragment, NamedWrite private static final String ROLES_FIELD = EntityType.ROLES.toString(); private static final String BACKEND_ROLES_FIELD = EntityType.BACKEND_ROLES.toString(); - private List users; + private Set users; - private List roles; + private Set roles; - private List backendRoles; + private Set backendRoles; - public SharedWithPerScope(List users, List roles, List backendRoles) { + public SharedWithPerScope(Set users, Set roles, Set backendRoles) { this.users = users; this.roles = roles; this.backendRoles = backendRoles; } public SharedWithPerScope(StreamInput in) throws IOException { - this.users = Arrays.asList(in.readStringArray()); - this.roles = Arrays.asList(in.readStringArray()); - this.backendRoles = Arrays.asList(in.readStringArray()); + this.users = Set.of(in.readStringArray()); + this.roles = Set.of(in.readStringArray()); + this.backendRoles = Set.of(in.readStringArray()); } - public List getUsers() { + public Set getUsers() { return users; } - public void setUsers(List users) { + public void setUsers(Set users) { this.users = users; } - public List getRoles() { + public Set getRoles() { return roles; } - public void setRoles(List roles) { + public void setRoles(Set roles) { this.roles = roles; } - public List getBackendRoles() { + public Set getBackendRoles() { return backendRoles; } - public void setBackendRoles(List backendRoles) { + public void setBackendRoles(Set backendRoles) { this.backendRoles = backendRoles; } @@ -163,9 +162,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } public static SharedWithPerScope fromXContent(XContentParser parser) throws IOException { - List users = new ArrayList<>(); - List roles = new ArrayList<>(); - List backendRoles = new ArrayList<>(); + Set users = new HashSet<>(); + Set roles = new HashSet<>(); + Set backendRoles = new HashSet<>(); XContentParser.Token token; String currentFieldName = null; @@ -194,7 +193,7 @@ public static SharedWithPerScope fromXContent(XContentParser parser) throws IOEx return new SharedWithPerScope(users, roles, backendRoles); } - private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, List values) throws IOException { + private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, Set values) throws IOException { if (values != null) { builder.field(fieldName, values); } else { From 3143796c2dbde07a85a61c053bd7df5816400e7f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 16:59:39 -0500 Subject: [PATCH 26/51] Updates revokeAccess signature to accept scopes to revoke access from Signed-off-by: Darshit Chanpura --- .../plugins/NoOpResourceAccessControlPlugin.java | 8 +++++++- .../opensearch/plugins/ResourceAccessControlPlugin.java | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index 07c3e45c6f89a..f784fcf2f7ec7 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -61,10 +61,16 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @param revokeAccess a map that contains entries of entities whose access should be revoked + * @param scopes a list of scopes to be checked for revoking access. If empty, all scopes will be checked. * @return null since security plugin is disabled in the cluster */ @Override - public ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess) { + public ResourceSharing revokeAccess( + String resourceId, + String systemIndexName, + Map> revokeAccess, + List scopes + ) { return null; } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 379fee4be090b..1193c34d7379d 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -61,9 +61,15 @@ public interface ResourceAccessControlPlugin { * @param resourceId if of the resource to be updated * @param systemIndexName index where this resource is defined * @param revokeAccess a map that contains entries of entities whose access should be revoked + * @param scopes Scopes to be checked for revoking access. If empty, all scopes will be checked. * @return the updated ResourceSharing record */ - ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess); + ResourceSharing revokeAccess( + String resourceId, + String systemIndexName, + Map> revokeAccess, + List scopes + ); /** * Deletes an entry from .resource_sharing index From e468f91dc9c34c90c2b318cb517b8ac1a55dae27 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 17:47:45 -0500 Subject: [PATCH 27/51] Convert sets to lists Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/ShareWith.java | 16 ++++++++-------- .../plugins/NoOpResourceAccessControlPlugin.java | 10 +++++----- .../plugins/ResourceAccessControlPlugin.java | 13 ++++--------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index 1761efb45af3e..c50a973cef52d 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -16,8 +16,8 @@ import org.opensearch.core.xcontent.XContentParser; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; /** * This class contains information about whom a resource is shared with and at what scope. @@ -39,17 +39,17 @@ */ public class ShareWith implements ToXContentFragment, NamedWriteable { - private final List sharedWithScopes; + private final Set sharedWithScopes; - public ShareWith(List sharedWithScopes) { + public ShareWith(Set sharedWithScopes) { this.sharedWithScopes = sharedWithScopes; } public ShareWith(StreamInput in) throws IOException { - this.sharedWithScopes = in.readList(SharedWithScope::new); + this.sharedWithScopes = in.readSet(SharedWithScope::new); } - public List getSharedWithScopes() { + public Set getSharedWithScopes() { return sharedWithScopes; } @@ -65,7 +65,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } public static ShareWith fromXContent(XContentParser parser) throws IOException { - List sharedWithScopes = new ArrayList<>(); + Set sharedWithScopes = new HashSet<>(); if (parser.currentToken() != XContentParser.Token.START_OBJECT) { parser.nextToken(); @@ -90,7 +90,7 @@ public String getWriteableName() { @Override public void writeTo(StreamOutput out) throws IOException { - out.writeList(sharedWithScopes); + out.writeCollection(sharedWithScopes); } @Override diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index f784fcf2f7ec7..171f7b2a18d28 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -12,8 +12,8 @@ import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; -import java.util.List; import java.util.Map; +import java.util.Set; /** * This plugin class defines a no-op implementation of Resource Plugin. @@ -29,10 +29,10 @@ public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlu * @return empty list */ @Override - public List listAccessibleResourcesInPlugin(String systemIndexName) { + public Set listAccessibleResourcesInPlugin(String systemIndexName) { // returns an empty list since security plugin is disabled // TODO: check whether this should return all entries in the given index - return List.of(); + return Set.of(); } /** @@ -68,8 +68,8 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar public ResourceSharing revokeAccess( String resourceId, String systemIndexName, - Map> revokeAccess, - List scopes + Map> revokeAccess, + Set scopes ) { return null; } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 1193c34d7379d..608a9a884c78d 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -12,8 +12,8 @@ import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; -import java.util.List; import java.util.Map; +import java.util.Set; /** * This interface determines presence of security plugin in the cluster. @@ -31,9 +31,9 @@ public interface ResourceAccessControlPlugin { /** * Returns all accessible resources for current user for a given system . * - * @return list of {@link ResourceSharing} items accessible by current user. + * @return set of {@link ResourceSharing} items accessible by current user. */ - List listAccessibleResourcesInPlugin(String systemIndex); + Set listAccessibleResourcesInPlugin(String systemIndex); /** * Checks whether current user has permission to given resource. @@ -64,12 +64,7 @@ public interface ResourceAccessControlPlugin { * @param scopes Scopes to be checked for revoking access. If empty, all scopes will be checked. * @return the updated ResourceSharing record */ - ResourceSharing revokeAccess( - String resourceId, - String systemIndexName, - Map> revokeAccess, - List scopes - ); + ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess, Set scopes); /** * Deletes an entry from .resource_sharing index From 3a0b4b1425ce7f6110f040b5e2ce9b4750091f1f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 5 Dec 2024 18:53:56 -0500 Subject: [PATCH 28/51] Upper-case the default scope Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/ResourceAccessScope.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java index 463936625522c..46138a5e788c6 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java @@ -16,6 +16,6 @@ * @opensearch.experimental */ public interface ResourceAccessScope { - String READ_ONLY = "read_only"; - String READ_WRITE = "read_write"; + String READ_ONLY = "READ_ONLY"; + String READ_WRITE = "READ_WRITE"; } From 0056807b99e7e00be0b23521596747d9703b5f21 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 11 Dec 2024 14:54:41 -0500 Subject: [PATCH 29/51] Adds concrete implementation of getResources in Noop RAC plugin Signed-off-by: Darshit Chanpura --- .../NoOpResourceAccessControlPlugin.java | 111 +++++++++++++++--- .../plugins/ResourceAccessControlPlugin.java | 21 ++-- 2 files changed, 105 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java index 171f7b2a18d28..6464a70202f2c 100644 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java @@ -11,55 +11,132 @@ import org.opensearch.accesscontrol.resources.EntityType; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; +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.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.unit.TimeValue; +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.index.query.QueryBuilders; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptService; +import org.opensearch.search.SearchHit; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; /** * This plugin class defines a no-op implementation of Resource Plugin. * * @opensearch.experimental */ -public class NoOpResourceAccessControlPlugin implements ResourceAccessControlPlugin { +public class NoOpResourceAccessControlPlugin extends Plugin implements ResourceAccessControlPlugin { + + 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 List.of(); + } /** - * Returns an empty list since security plugin is not defined. - * This method alone doesn't determine permissions. + * Returns a list of all resource ids in provided systemIndex + * @param resourceIndexName index where resources are stored * - * @return empty list + * @return Set of resource ids */ @Override - public Set listAccessibleResourcesInPlugin(String systemIndexName) { - // returns an empty list since security plugin is disabled - // TODO: check whether this should return all entries in the given index - return Set.of(); + public Set getAccessibleResourcesForCurrentUser(String resourceIndexName) { + // TODO: check whether this should return '*'. + // This would indicate to plugins that user has access to all resources since security is disabled + // and would eliminate the need for all the code below + // return Set.of("*"); + + SearchRequest searchRequest = new SearchRequest(resourceIndexName); + SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.query(QueryBuilders.matchAllQuery()); + sourceBuilder.fetchSource(false); + sourceBuilder.size(10000); + + searchRequest.scroll(TimeValue.timeValueMinutes(1)); + searchRequest.source(sourceBuilder); + + Set allDocumentIds = new HashSet<>(); + + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + String scrollId = searchResponse.getScrollId(); + SearchHit[] searchHits = searchResponse.getHits().getHits(); + + while (searchHits != null && searchHits.length > 0) { + Arrays.stream(searchHits).map(SearchHit::getId).forEach(allDocumentIds::add); + + SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); + scrollRequest.scroll(TimeValue.timeValueMinutes(1)); + searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); + scrollId = searchResponse.getScrollId(); + searchHits = searchResponse.getHits().getHits(); + } + + ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); + clearScrollRequest.addScrollId(scrollId); + client.clearScroll(clearScrollRequest); + + return allDocumentIds; } /** * @param resourceId the resource on which access is to be checked - * @param systemIndexName where the resource exists + * @param resourceIndex where the resource exists * @param scope the type of access being requested - * @return true + * @return true since security plugin is disabled in the cluster */ @Override - public boolean hasPermission(String resourceId, String systemIndexName, String scope) { + public boolean hasPermission(String resourceId, String resourceIndex, String scope) { return true; } /** * @param resourceId if of the resource to be updated - * @param systemIndexName index where this resource is defined + * @param resourceIndex index where this resource is stored * @param shareWith a map that contains entries of entities with whom this resource should be shared with * @return null since security plugin is disabled in the cluster */ @Override - public ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith) { + public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) { return null; } /** * @param resourceId if of the resource to be updated - * @param systemIndexName index where this resource is defined + * @param resourceIndex index where this resource is stored * @param revokeAccess a map that contains entries of entities whose access should be revoked * @param scopes a list of scopes to be checked for revoking access. If empty, all scopes will be checked. * @return null since security plugin is disabled in the cluster @@ -67,7 +144,7 @@ public ResourceSharing shareWith(String resourceId, String systemIndexName, Shar @Override public ResourceSharing revokeAccess( String resourceId, - String systemIndexName, + String resourceIndex, Map> revokeAccess, Set scopes ) { @@ -76,11 +153,11 @@ public ResourceSharing revokeAccess( /** * @param resourceId if of the resource to be updated - * @param systemIndexName index where this resource is defined + * @param resourceIndex index where this resource is stored * @return false since security plugin is disabled */ @Override - public boolean deleteResourceSharingRecord(String resourceId, String systemIndexName) { + public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) { return false; } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 608a9a884c78d..dd630d228d573 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -29,50 +29,51 @@ public interface ResourceAccessControlPlugin { /** - * Returns all accessible resources for current user for a given system . + * Returns all accessible resources for current user for a given plugin index. + * @param resourceIndex index where the resource exists * * @return set of {@link ResourceSharing} items accessible by current user. */ - Set listAccessibleResourcesInPlugin(String systemIndex); + Set getAccessibleResourcesForCurrentUser(String resourceIndex); /** * Checks whether current user has permission to given resource. * * @param resourceId the resource on which access is to be checked - * @param systemIndexName where the resource exists + * @param resourceIndex where the resource exists * @param scope the scope being requested * @return true if current user has access, false otherwise */ - boolean hasPermission(String resourceId, String systemIndexName, String scope); + boolean hasPermission(String resourceId, String resourceIndex, String scope); /** * Adds an entity to the share-with. Resource needs to be in restricted mode. * Creates a resource sharing record if one doesn't exist. * @param resourceId id of the resource to be updated - * @param systemIndexName index where this resource is defined + * @param resourceIndex index where this resource is stored * @param shareWith an object that contains entries of entities with whom the resource should be shared with * @return updated resource sharing record */ - ResourceSharing shareWith(String resourceId, String systemIndexName, ShareWith shareWith); + ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith); /** * Revokes given permission to a resource * * @param resourceId if of the resource to be updated - * @param systemIndexName index where this resource is defined + * @param resourceIndex index where this resource is stored * @param revokeAccess a map that contains entries of entities whose access should be revoked * @param scopes Scopes to be checked for revoking access. If empty, all scopes will be checked. * @return the updated ResourceSharing record */ - ResourceSharing revokeAccess(String resourceId, String systemIndexName, Map> revokeAccess, Set scopes); + ResourceSharing revokeAccess(String resourceId, String resourceIndex, Map> revokeAccess, Set scopes); /** * Deletes an entry from .resource_sharing index * @param resourceId if of the resource to be updated - * @param systemIndexName index where this resource is defined + * @param resourceIndex index where this resource is stored * @return true if resource record was deleted, false otherwise */ - boolean deleteResourceSharingRecord(String resourceId, String systemIndexName); + boolean deleteResourceSharingRecord(String resourceId, String resourceIndex); /** * TODO check if this method is needed From 193112b55e909286b608a8eeae4366e7673e052f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 13 Dec 2024 01:22:36 -0500 Subject: [PATCH 30/51] Updates the ResourceAccessControlPlugin to have noop actions, renames noopRACplugin to defaultRACplugin since we now list all accessible resources and updates comments Signed-off-by: Darshit Chanpura --- .../resources/ResourceService.java | 17 +- .../DefaultResourceAccessControlPlugin.java | 137 ++++++++++++++ .../main/java/org/opensearch/node/Node.java | 2 +- .../NoOpResourceAccessControlPlugin.java | 172 ------------------ .../plugins/ResourceAccessControlPlugin.java | 31 +++- 5 files changed, 175 insertions(+), 184 deletions(-) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java delete mode 100644 server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 1a5fd9c8d8701..c20a144f6384d 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -11,9 +11,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; -import org.opensearch.plugins.NoOpResourceAccessControlPlugin; +import org.opensearch.accesscontrol.resources.fallback.DefaultResourceAccessControlPlugin; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; import org.opensearch.plugins.ResourceAccessControlPlugin; import org.opensearch.plugins.ResourcePlugin; +import org.opensearch.threadpool.ThreadPool; import java.util.List; import java.util.stream.Collectors; @@ -29,12 +32,18 @@ public class ResourceService { private final ResourceAccessControlPlugin resourceACPlugin; private final List resourcePlugins; - public ResourceService(final List resourceACPlugins, List resourcePlugins) { + @Inject + public ResourceService( + final List resourceACPlugins, + List resourcePlugins, + Client client, + ThreadPool threadPool + ) { this.resourcePlugins = resourcePlugins; if (resourceACPlugins.isEmpty()) { - log.info("Security plugin disabled: Using NoOpResourceAccessControlPlugin"); - resourceACPlugin = new NoOpResourceAccessControlPlugin(); + log.info("Security plugin disabled: Using DefaultResourceAccessControlPlugin"); + resourceACPlugin = new DefaultResourceAccessControlPlugin(client, threadPool); } else if (resourceACPlugins.size() == 1) { log.info("Security plugin enabled: Using OpenSearchSecurityPlugin"); resourceACPlugin = resourceACPlugins.get(0); diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java new file mode 100644 index 0000000000000..7d3b2d43f3780 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java @@ -0,0 +1,137 @@ +/* + * 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.accesscontrol.resources.fallback; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; +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.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.search.SearchHit; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.threadpool.ThreadPool; + +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +/** + * This plugin class defines a pass-through implementation of ResourceAccessControlPlugin. + * + * @opensearch.experimental + */ +@SuppressWarnings("removal") +public class DefaultResourceAccessControlPlugin extends Plugin implements ResourceAccessControlPlugin { + + private static final Logger log = LogManager.getLogger(DefaultResourceAccessControlPlugin.class); + + private Client client; + + private final ThreadPool threadPool; + + @Inject + public DefaultResourceAccessControlPlugin(Client client, ThreadPool threadPool) { + this.client = client; + this.threadPool = threadPool; + } + + /** + * Returns a list of all resource ids in provided systemIndex + * @param resourceIndex index where resources are stored + * + * @return Set of resource ids + */ + @Override + public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { + final Set documents = new HashSet<>(); + final TimeValue scrollTimeout = TimeValue.timeValueMinutes(1); + String scrollId; + + log.info("Searching for accessible resources in index {}", resourceIndex); + // stashContext is required in case resourceIndex is a system index + try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + final SearchRequest searchRequest = buildSearchRequest(resourceIndex, scrollTimeout); + + SearchResponse searchResponse = client.search(searchRequest).actionGet(); + scrollId = searchResponse.getScrollId(); + SearchHit[] searchHits = searchResponse.getHits().getHits(); + + while (searchHits != null && searchHits.length > 0) { + parseAndAddToDocuments(Arrays.stream(searchHits).map(SearchHit::getSourceAsMap), clazz, documents); + + final SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); + scrollRequest.scroll(scrollTimeout); + searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); + scrollId = searchResponse.getScrollId(); + searchHits = searchResponse.getHits().getHits(); + } + + final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); + clearScrollRequest.addScrollId(scrollId); + client.clearScroll(clearScrollRequest).actionGet(); + } catch (Exception e) { + log.error("Exception: {}", e.getMessage()); + return Set.of(); + } + log.info("Found {} documents", documents.size()); + return Collections.unmodifiableSet(documents); + } + + private SearchRequest buildSearchRequest(String resourceIndex, TimeValue scrollTimeout) { + final SearchRequest searchRequest = new SearchRequest(resourceIndex); + final SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); + sourceBuilder.query(QueryBuilders.matchAllQuery()); + sourceBuilder.fetchSource(true); + sourceBuilder.size(10000); + + searchRequest.scroll(scrollTimeout); + searchRequest.source(sourceBuilder); + + return searchRequest; + } + + private static T parse(Map sourceMap, Class clazz) { + return AccessController.doPrivileged((PrivilegedAction) () -> { + try { + final T instance = clazz.getDeclaredConstructor().newInstance(); + for (Field field : clazz.getDeclaredFields()) { + field.setAccessible(true); + Object value = sourceMap.get(field.getName()); + if (value != null) { + field.set(instance, value); + } + } + return instance; + } catch (Exception e) { + throw new OpenSearchException("Failed to parse source map into " + clazz.getName(), e); + } + }); + } + + private static void parseAndAddToDocuments(Stream> searchHits, Class clazz, Set documents) { + searchHits.map(hit -> parse(hit, clazz)).forEach(documents::add); + } + +} diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index 27f6030dee196..5964c15230820 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -1115,7 +1115,7 @@ protected Node( ResourceAccessControlPlugin.class ); final List resourcePlugins = pluginsService.filterPlugins(ResourcePlugin.class); - ResourceService resourceService = new ResourceService(resourceAccessControlPlugins, resourcePlugins); + ResourceService resourceService = new ResourceService(resourceAccessControlPlugins, resourcePlugins, client, threadPool); final RestController restController = actionModule.getRestController(); diff --git a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java deleted file mode 100644 index 6464a70202f2c..0000000000000 --- a/server/src/main/java/org/opensearch/plugins/NoOpResourceAccessControlPlugin.java +++ /dev/null @@ -1,172 +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.plugins; - -import org.opensearch.accesscontrol.resources.EntityType; -import org.opensearch.accesscontrol.resources.ResourceSharing; -import org.opensearch.accesscontrol.resources.ShareWith; -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.client.Client; -import org.opensearch.cluster.metadata.IndexNameExpressionResolver; -import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.unit.TimeValue; -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.index.query.QueryBuilders; -import org.opensearch.repositories.RepositoriesService; -import org.opensearch.script.ScriptService; -import org.opensearch.search.SearchHit; -import org.opensearch.search.builder.SearchSourceBuilder; -import org.opensearch.threadpool.ThreadPool; -import org.opensearch.watcher.ResourceWatcherService; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; - -/** - * This plugin class defines a no-op implementation of Resource Plugin. - * - * @opensearch.experimental - */ -public class NoOpResourceAccessControlPlugin extends Plugin implements ResourceAccessControlPlugin { - - 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 List.of(); - } - - /** - * Returns a list of all resource ids in provided systemIndex - * @param resourceIndexName index where resources are stored - * - * @return Set of resource ids - */ - @Override - public Set getAccessibleResourcesForCurrentUser(String resourceIndexName) { - // TODO: check whether this should return '*'. - // This would indicate to plugins that user has access to all resources since security is disabled - // and would eliminate the need for all the code below - // return Set.of("*"); - - SearchRequest searchRequest = new SearchRequest(resourceIndexName); - SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); - sourceBuilder.query(QueryBuilders.matchAllQuery()); - sourceBuilder.fetchSource(false); - sourceBuilder.size(10000); - - searchRequest.scroll(TimeValue.timeValueMinutes(1)); - searchRequest.source(sourceBuilder); - - Set allDocumentIds = new HashSet<>(); - - SearchResponse searchResponse = client.search(searchRequest).actionGet(); - String scrollId = searchResponse.getScrollId(); - SearchHit[] searchHits = searchResponse.getHits().getHits(); - - while (searchHits != null && searchHits.length > 0) { - Arrays.stream(searchHits).map(SearchHit::getId).forEach(allDocumentIds::add); - - SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); - scrollRequest.scroll(TimeValue.timeValueMinutes(1)); - searchResponse = client.execute(SearchScrollAction.INSTANCE, scrollRequest).actionGet(); - scrollId = searchResponse.getScrollId(); - searchHits = searchResponse.getHits().getHits(); - } - - ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); - clearScrollRequest.addScrollId(scrollId); - client.clearScroll(clearScrollRequest); - - return allDocumentIds; - } - - /** - * @param resourceId the resource on which access is to be checked - * @param resourceIndex where the resource exists - * @param scope the type of access being requested - * @return true since security plugin is disabled in the cluster - */ - @Override - public boolean hasPermission(String resourceId, String resourceIndex, String scope) { - return true; - } - - /** - * @param resourceId if of the resource to be updated - * @param resourceIndex index where this resource is stored - * @param shareWith a map that contains entries of entities with whom this resource should be shared with - * @return null since security plugin is disabled in the cluster - */ - @Override - public ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) { - return null; - } - - /** - * @param resourceId if of the resource to be updated - * @param resourceIndex index where this resource is stored - * @param revokeAccess a map that contains entries of entities whose access should be revoked - * @param scopes a list of scopes to be checked for revoking access. If empty, all scopes will be checked. - * @return null since security plugin is disabled in the cluster - */ - @Override - public ResourceSharing revokeAccess( - String resourceId, - String resourceIndex, - Map> revokeAccess, - Set scopes - ) { - return null; - } - - /** - * @param resourceId if of the resource to be updated - * @param resourceIndex index where this resource is stored - * @return false since security plugin is disabled - */ - @Override - public boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) { - return false; - } - - /** - * @return false since security plugin is disabled - */ - @Override - public boolean deleteAllResourceSharingRecordsForCurrentUser() { - return false; - } - -} diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index dd630d228d573..5e02024adc701 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -31,10 +31,12 @@ public interface ResourceAccessControlPlugin { /** * Returns all accessible resources for current user for a given plugin index. * @param resourceIndex index where the resource exists - * + * @param clazz class of the resource. Required to parse the resource object retrieved from resourceIndex * @return set of {@link ResourceSharing} items accessible by current user. */ - Set getAccessibleResourcesForCurrentUser(String resourceIndex); + default Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { + return Set.of(); + } /** * Checks whether current user has permission to given resource. @@ -44,7 +46,9 @@ public interface ResourceAccessControlPlugin { * @param scope the scope being requested * @return true if current user has access, false otherwise */ - boolean hasPermission(String resourceId, String resourceIndex, String scope); + default boolean hasPermission(String resourceId, String resourceIndex, String scope) { + return true; + } /** * Adds an entity to the share-with. Resource needs to be in restricted mode. @@ -54,7 +58,9 @@ public interface ResourceAccessControlPlugin { * @param shareWith an object that contains entries of entities with whom the resource should be shared with * @return updated resource sharing record */ - ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith); + default ResourceSharing shareWith(String resourceId, String resourceIndex, ShareWith shareWith) { + return null; + } /** * Revokes given permission to a resource @@ -65,7 +71,14 @@ public interface ResourceAccessControlPlugin { * @param scopes Scopes to be checked for revoking access. If empty, all scopes will be checked. * @return the updated ResourceSharing record */ - ResourceSharing revokeAccess(String resourceId, String resourceIndex, Map> revokeAccess, Set scopes); + default ResourceSharing revokeAccess( + String resourceId, + String resourceIndex, + Map> revokeAccess, + Set scopes + ) { + return null; + } /** * Deletes an entry from .resource_sharing index @@ -73,14 +86,18 @@ public interface ResourceAccessControlPlugin { * @param resourceIndex index where this resource is stored * @return true if resource record was deleted, false otherwise */ - boolean deleteResourceSharingRecord(String resourceId, String resourceIndex); + default boolean deleteResourceSharingRecord(String resourceId, String resourceIndex) { + return false; + } /** * TODO check if this method is needed * Deletes all entries from .resource_sharing index where current user is the creator of the resource * @return true if resource record was deleted, false otherwise */ - boolean deleteAllResourceSharingRecordsForCurrentUser(); + default boolean deleteAllResourceSharingRecordsForCurrentUser() { + return false; + } // TODO: Check whether methods for bulk updates are required } From cbbefa677ee77c0b8c51e49889ca0305d290ddd6 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 13 Dec 2024 01:23:12 -0500 Subject: [PATCH 31/51] Adds ReflectPermissions for DefaultResourceAccessControlPlugin Signed-off-by: Darshit Chanpura --- .../main/resources/org/opensearch/bootstrap/security.policy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index 22e445f7d9022..5cd546adf3eda 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -52,6 +52,12 @@ grant codeBase "${codebase.opensearch}" { permission org.opensearch.secure_sm.ThreadContextPermission "stashWithOrigin"; }; +//// Permission specific to DefaultResourceAccessControlPlugin +grant codeBase "${codebase.opensearch}" { + // ReflectPermission granted only for the DefaultResourceAccessControlPlugin to allow parsing the Resources + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; +}; + //// Very special jar permissions: //// These are dangerous permissions that we don't want to grant to everything. From b58308e4cd8ed1daf5100d4998733e8e4917dab5 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 17 Dec 2024 11:28:03 -0500 Subject: [PATCH 32/51] Adds missing package-info Signed-off-by: Darshit Chanpura --- .../resources/fallback/package-info.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java new file mode 100644 index 0000000000000..1c0ab7a081f25 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/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. + */ + +/** + * This package defines all default implementations of Resource Sharing and Access Control which will be utilized when security is disabled + */ +package org.opensearch.accesscontrol.resources.fallback; From b25a9a7bcd6fe64244881a86e662d1d6c3b8640b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 14:35:31 -0500 Subject: [PATCH 33/51] Adds unit and integration tests Signed-off-by: Darshit Chanpura --- .../DefaultResourceAccessControlPluginIT.java | 107 +++++++ .../fallback/SampleTestResourcePlugin.java | 79 +++++ .../resources/ResourceService.java | 2 +- .../resources/CreatedByTests.java | 296 ++++++++++++++++++ .../resources/ResourceServiceTests.java | 213 +++++++++++++ .../resources/ShareWithTests.java | 201 ++++++++++++ 6 files changed, 897 insertions(+), 1 deletion(-) create mode 100644 server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java create mode 100644 server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.java create mode 100644 server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java create mode 100644 server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java create mode 100644 server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java new file mode 100644 index 0000000000000..4abf412159ea6 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java @@ -0,0 +1,107 @@ +/* + * 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.accesscontrol.resources.fallback; + +import org.opensearch.client.Client; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.test.InternalTestCluster; +import org.opensearch.test.OpenSearchIntegTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.hamcrest.MatcherAssert; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static org.opensearch.accesscontrol.resources.fallback.SampleTestResourcePlugin.SAMPLE_TEST_INDEX; +import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +public class DefaultResourceAccessControlPluginIT extends OpenSearchIntegTestCase { + @Override + protected Collection> nodePlugins() { + return List.of(SampleTestResourcePlugin.class); + } + + public void testGetResources() throws IOException { + final Client client = client(); + + createIndex(SAMPLE_TEST_INDEX); + indexSampleDocuments(); + + Set resources; + try ( + InternalTestCluster cluster = internalCluster(); + DefaultResourceAccessControlPlugin plugin = new DefaultResourceAccessControlPlugin( + client, + cluster.getInstance(ThreadPool.class) + ) + ) { + + resources = plugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, 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 testSampleResourcePluginCallsDefaultPlugin() throws IOException { + createIndex(SAMPLE_TEST_INDEX); + indexSampleDocuments(); + + ResourceAccessControlPlugin racPlugin = SampleTestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); + + Set resources = racPlugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, 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")))); + } + + 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(); + } + } + + public static class TestResource { + public String id; + public String name; + + public TestResource() {} + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.java new file mode 100644 index 0000000000000..20251e7ac7ba3 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.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.accesscontrol.resources.fallback; + +import org.opensearch.accesscontrol.resources.ResourceService; +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.plugins.Plugin; +import org.opensearch.plugins.ResourcePlugin; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +// Sample test resource plugin +public class SampleTestResourcePlugin extends Plugin implements ResourcePlugin { + + public static final String SAMPLE_TEST_INDEX = ".sample_test_resource"; + + @Override + public String getResourceType() { + return ""; + } + + @Override + public String getResourceIndex() { + return SAMPLE_TEST_INDEX; + } + + @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/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index c20a144f6384d..70d9c837931d8 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -66,6 +66,6 @@ public ResourceAccessControlPlugin getResourceAccessControlPlugin() { * List active plugins that define resources */ public List listResourcePlugins() { - return resourcePlugins; + return List.copyOf(resourcePlugins); } } diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java new file mode 100644 index 0000000000000..52b0bfd01c7fc --- /dev/null +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java @@ -0,0 +1,296 @@ +package org.opensearch.accesscontrol.resources; + +import org.opensearch.common.io.stream.BytesStreamOutput; +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.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 org.hamcrest.MatcherAssert; + +import java.io.IOException; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class CreatedByTests extends OpenSearchTestCase { + + public void testCreatedByConstructorWithValidUser() { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(expectedUser); + + MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getUser()))); + } + + public void testCreatedByFromStreamInput() throws IOException { + String expectedUser = "testUser"; + + try (BytesStreamOutput out = new BytesStreamOutput()) { + out.writeString(expectedUser); + + StreamInput in = out.bytes().streamInput(); + + CreatedBy createdBy = new CreatedBy(in); + + MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getUser()))); + } + } + + 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(""); + MatcherAssert.assertThat("", equalTo(createdBy.getUser())); + } + + 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(longUsername); + MatcherAssert.assertThat(longUsername, equalTo(createdBy.getUser())); + } + + public void testCreatedByWithUnicodeCharacters() { + String unicodeUsername = "用户こんにちは"; + CreatedBy createdBy = new CreatedBy(unicodeUsername); + MatcherAssert.assertThat(unicodeUsername, equalTo(createdBy.getUser())); + } + + public void testFromXContentThrowsExceptionWhenUserFieldIsMissing() throws IOException { + String json = "{}"; + IllegalArgumentException exception; + try (XContentParser parser = createParser(XContentType.JSON.xContent(), json)) { + + exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + + MatcherAssert.assertThat("user field 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 testFromXContentWithMissingUser() throws IOException { + String json = "{}"; + IllegalArgumentException exception; + try (XContentParser parser = createParser(JsonXContent.jsonXContent, json)) { + parser.nextToken(); // Move to the start object token + + exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + + MatcherAssert.assertThat("user field is required", equalTo(exception.getMessage())); + } + + public void testFromXContentWithMissingUserField() throws IOException { + String jsonWithoutUser = "{\"someOtherField\": \"value\"}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithoutUser)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + 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 = XContentType.JSON.xContent().createParser(xContentRegistry(), null, json); + + CreatedBy createdBy = CreatedBy.fromXContent(parser); + + assertNotNull(createdBy); + MatcherAssert.assertThat("testUser", equalTo(createdBy.getUser())); + } + + public void testGetUserReturnsCorrectValue() { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(expectedUser); + + String actualUser = createdBy.getUser(); + + MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); + } + + public void testGetUserWithNullString() { + + CreatedBy createdBy = new CreatedBy((String) null); + assertNull("getUser should return null when initialized with null", createdBy.getUser()); + } + + public void testGetWriteableNameReturnsCorrectString() { + CreatedBy createdBy = new CreatedBy("testUser"); + MatcherAssert.assertThat("created_by", equalTo(createdBy.getWriteableName())); + } + + public void testSetUserWithEmptyString() { + CreatedBy createdBy = new CreatedBy("initialUser"); + createdBy.setUser(""); + MatcherAssert.assertThat("", equalTo(createdBy.getUser())); + } + + public void testToStringWithEmptyUser() { + CreatedBy createdBy = new CreatedBy(""); + String result = createdBy.toString(); + MatcherAssert.assertThat("CreatedBy {user=''}", equalTo(result)); + } + + public void testToStringWithNullUser() { + CreatedBy createdBy = new CreatedBy((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(longUserName); + String result = createdBy.toString(); + assertTrue(result.startsWith("CreatedBy {user='")); + assertTrue(result.endsWith("'}")); + MatcherAssert.assertThat(1019, equalTo(result.length())); + } + + public void testToXContentWithEmptyUser() throws IOException { + CreatedBy createdBy = new CreatedBy(""); + 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("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(longUserName); + BytesStreamOutput out = new BytesStreamOutput(); + createdBy.writeTo(out); + assertTrue(out.size() > 65536); + } + + public void test_createdByToStringReturnsCorrectFormat() { + String testUser = "testUser"; + CreatedBy createdBy = new CreatedBy(testUser); + + String expected = "CreatedBy {user='" + testUser + "'}"; + String actual = createdBy.toString(); + + MatcherAssert.assertThat(expected, equalTo(actual)); + } + + public void test_fromXContent_missingUserField() throws IOException { + String json = "{}"; + IllegalArgumentException exception; + try (XContentParser parser = createParser(XContentType.JSON.xContent(), json)) { + parser.nextToken(); // Move to the start object token + + exception = assertThrows(IllegalArgumentException.class, () -> { CreatedBy.fromXContent(parser); }); + } + + MatcherAssert.assertThat("user field is required", equalTo(exception.getMessage())); + } + + public void test_toXContent_serializesCorrectly() throws IOException { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(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(expectedUser); + + BytesStreamOutput out = new BytesStreamOutput(); + createdBy.writeTo(out); + + StreamInput in = out.bytes().streamInput(); + String actualUser = in.readString(); + + MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); + } + +} diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java new file mode 100644 index 0000000000000..63c2cd16253b3 --- /dev/null +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java @@ -0,0 +1,213 @@ +package org.opensearch.accesscontrol.resources; + +import org.opensearch.OpenSearchException; +import org.opensearch.accesscontrol.resources.fallback.DefaultResourceAccessControlPlugin; +import org.opensearch.client.Client; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.plugins.ResourcePlugin; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.hamcrest.MatcherAssert; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.hamcrest.Matchers.equalTo; +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(); + + assertEquals(mockPlugin, 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); + assertTrue(result instanceof DefaultResourceAccessControlPlugin); + } + + 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(ResourcePlugin.class)); + expectedResourcePlugins.add(mock(ResourcePlugin.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(ResourcePlugin.class)); + + ResourceService resourceService = new ResourceService(emptyACPlugins, resourcePlugins, client, threadPool); + + Thread modifierThread = new Thread(() -> { resourcePlugins.add(mock(ResourcePlugin.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); + assertTrue(result.isEmpty()); + } + + public void testListResourcePlugins_immutability() { + setup(); + List emptyACPlugins = Collections.emptyList(); + List resourcePlugins = new ArrayList<>(); + resourcePlugins.add(mock(ResourcePlugin.class)); + + ResourceService resourceService = new ResourceService(emptyACPlugins, resourcePlugins, client, threadPool); + + List result = resourceService.listResourcePlugins(); + + assertThrows(UnsupportedOperationException.class, () -> { result.add(mock(ResourcePlugin.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(ResourcePlugin.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); + + assertTrue(resourceService.getResourceAccessControlPlugin() instanceof DefaultResourceAccessControlPlugin); + assertEquals(resourcePlugins, 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); + assertEquals(mockPlugin, resourceService.getResourceAccessControlPlugin()); + assertEquals(resourcePlugins, resourceService.listResourcePlugins()); + } +} diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java new file mode 100644 index 0000000000000..c1c7d48802fc8 --- /dev/null +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java @@ -0,0 +1,201 @@ +package org.opensearch.accesscontrol.resources; + +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.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.test.OpenSearchTestCase; +import org.hamcrest.MatcherAssert; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.mockito.Mockito; + +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 { + + 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.SharedWithPerScope sharedWithPerScope = scope.getSharedWithPerScope(); + assertNotNull(sharedWithPerScope); + MatcherAssert.assertThat(1, equalTo(sharedWithPerScope.getUsers().size())); + MatcherAssert.assertThat("user1", equalTo(sharedWithPerScope.getUsers().iterator().next())); + MatcherAssert.assertThat(0, equalTo(sharedWithPerScope.getRoles().size())); + MatcherAssert.assertThat(0, equalTo(sharedWithPerScope.getBackendRoles().size())); + } + + 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(); + assertEquals(2, scopes.size()); + + for (SharedWithScope scope : scopes) { + SharedWithScope.SharedWithPerScope perScope = scope.getSharedWithPerScope(); + if (scope.getScope().equals(ResourceAccessScope.READ_ONLY)) { + assertEquals(2, perScope.getUsers().size()); + assertEquals(1, perScope.getRoles().size()); + assertEquals(1, perScope.getBackendRoles().size()); + } else if (scope.getScope().equals(ResourceAccessScope.READ_WRITE)) { + assertEquals(1, perScope.getUsers().size()); + assertEquals(2, perScope.getRoles().size()); + assertEquals(0, perScope.getBackendRoles().size()); + } + } + } + + 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); + assertTrue(result.getSharedWithScopes().isEmpty()); + } + + public void testToXContentBuildsCorrectly() throws IOException { + SharedWithScope scope = new SharedWithScope("scope1", new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of())); + + Set scopes = new HashSet<>(); + scopes.add(scope); + + ShareWith shareWith = new ShareWith(scopes); + + XContentBuilder builder = JsonXContent.contentBuilder(); + + shareWith.toXContent(builder, null); + + String result = builder.toString(); + + String expected = "{\"scope1\":{\"users\":[],\"roles\":[],\"backend_roles\":[]}}"; + + 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.SharedWithPerScope(Set.of(), Set.of(), Set.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.SharedWithPerScope(Set.of(), Set.of(), Set.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); + + assertTrue(shareWith.getSharedWithScopes().isEmpty()); + } + + 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.SharedWithPerScope(Set.of(), Set.of(), Set.of())) + ); + sharedWithScopes.add( + new SharedWithScope(ResourceAccessScope.READ_WRITE, new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of())) + ); + + ShareWith shareWith = new ShareWith(sharedWithScopes); + + shareWith.writeTo(mockStreamOutput); + + verify(mockStreamOutput, times(1)).writeCollection(eq(sharedWithScopes)); + } + +} From eb43578e173ee8d70f5d347d659be40a1b605ac8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 14:40:51 -0500 Subject: [PATCH 34/51] Adds missing experimental annotations Signed-off-by: Darshit Chanpura --- .../opensearch/accesscontrol/resources/ResourceService.java | 2 +- .../accesscontrol/resources/fallback/package-info.java | 2 ++ .../org/opensearch/accesscontrol/resources/package-info.java | 4 +++- .../org/opensearch/plugins/ResourceAccessControlPlugin.java | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 70d9c837931d8..7ea58cf223aa5 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -25,7 +25,7 @@ * Resource access control for OpenSearch * * @opensearch.experimental - * */ + */ public class ResourceService { private static final Logger log = LogManager.getLogger(ResourceService.class); diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java index 1c0ab7a081f25..5658a60d641d6 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java @@ -8,5 +8,7 @@ /** * This package defines all default implementations of Resource Sharing and Access Control which will be utilized when security is disabled + * + * @opensearch.experimental */ package org.opensearch.accesscontrol.resources.fallback; diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java index b8ad5237a60ca..a0dfd013dc5c3 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java @@ -7,6 +7,8 @@ */ /** - * This package defines all classes required for Resource Sharing and Access Control + * This package defines all classes required for Resource Sharing and Access Control in OpenSearch + * + * @opensearch.experimental */ package org.opensearch.accesscontrol.resources; diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 5e02024adc701..7d93065d6983c 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -22,7 +22,7 @@ * In clusters, where security plugin is disabled these requests will be pass-through via a No-op implementation. * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit ... * If security plugin is disabled, all resources will be considered public by default. - * TODO: add documentation around "how to use" + * Refer to the sample-resource-plugin introduced here to understand the usage of this class. * * @opensearch.experimental */ From 686f03757a455db4082163740cff86f0acff4f56 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 17:27:51 -0500 Subject: [PATCH 35/51] Uses jackson object-mapper to read resource class and updates the integration tests as well as security policy Signed-off-by: Darshit Chanpura --- libs/core/build.gradle | 2 + .../DefaultResourceAccessControlPluginIT.java | 47 +++++++------------ ...rcePlugin.java => TestResourcePlugin.java} | 17 ++++++- .../DefaultResourceAccessControlPlugin.java | 40 +++++++--------- .../org/opensearch/bootstrap/security.policy | 8 ++-- 5 files changed, 55 insertions(+), 59 deletions(-) rename server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/{SampleTestResourcePlugin.java => TestResourcePlugin.java} (85%) diff --git a/libs/core/build.gradle b/libs/core/build.gradle index 0cf2cd0bf92b6..f381aba7c7549 100644 --- a/libs/core/build.gradle +++ b/libs/core/build.gradle @@ -40,6 +40,8 @@ dependencies { api project(':libs:opensearch-common') api "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" + api "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + api "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" // lucene api "org.apache.lucene:lucene-core:${versions.lucene}" diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java index 4abf412159ea6..0848b9fb48f38 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java @@ -12,7 +12,6 @@ import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.plugins.Plugin; import org.opensearch.plugins.ResourceAccessControlPlugin; -import org.opensearch.test.InternalTestCluster; import org.opensearch.test.OpenSearchIntegTestCase; import org.opensearch.threadpool.ThreadPool; import org.hamcrest.MatcherAssert; @@ -22,7 +21,7 @@ import java.util.List; import java.util.Set; -import static org.opensearch.accesscontrol.resources.fallback.SampleTestResourcePlugin.SAMPLE_TEST_INDEX; +import static org.opensearch.accesscontrol.resources.fallback.TestResourcePlugin.SAMPLE_TEST_INDEX; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasProperty; @@ -32,7 +31,7 @@ public class DefaultResourceAccessControlPluginIT extends OpenSearchIntegTestCase { @Override protected Collection> nodePlugins() { - return List.of(SampleTestResourcePlugin.class); + return List.of(TestResourcePlugin.class); } public void testGetResources() throws IOException { @@ -41,33 +40,34 @@ public void testGetResources() throws IOException { createIndex(SAMPLE_TEST_INDEX); indexSampleDocuments(); - Set resources; + Set resources; try ( - InternalTestCluster cluster = internalCluster(); DefaultResourceAccessControlPlugin plugin = new DefaultResourceAccessControlPlugin( client, - cluster.getInstance(ThreadPool.class) + internalCluster().getInstance(ThreadPool.class) ) ) { + resources = plugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, TestResourcePlugin.TestResource.class); - resources = plugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, TestResource.class); - } - - assertNotNull(resources); - MatcherAssert.assertThat(resources, hasSize(2)); + assertNotNull(resources); + MatcherAssert.assertThat(resources, hasSize(2)); - MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("1")))); - MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2")))); + MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("1")))); + MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2")))); + } } - public void testSampleResourcePluginCallsDefaultPlugin() throws IOException { + public void testSampleResourcePluginCallsDefaultRACPlugin() throws IOException { createIndex(SAMPLE_TEST_INDEX); indexSampleDocuments(); - ResourceAccessControlPlugin racPlugin = SampleTestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); - Set resources = racPlugin.getAccessibleResourcesForCurrentUser(SAMPLE_TEST_INDEX, TestResource.class); + Set resources = racPlugin.getAccessibleResourcesForCurrentUser( + SAMPLE_TEST_INDEX, + TestResourcePlugin.TestResource.class + ); assertNotNull(resources); MatcherAssert.assertThat(resources, hasSize(2)); @@ -89,19 +89,4 @@ private void indexSampleDocuments() throws IOException { client.admin().indices().prepareRefresh(SAMPLE_TEST_INDEX).get(); } } - - public static class TestResource { - public String id; - public String name; - - public TestResource() {} - - public String getId() { - return id; - } - - public String getName() { - return name; - } - } } diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/TestResourcePlugin.java similarity index 85% rename from server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.java rename to server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/TestResourcePlugin.java index 20251e7ac7ba3..bf2e0859f8f8b 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/SampleTestResourcePlugin.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/TestResourcePlugin.java @@ -21,7 +21,7 @@ import java.util.List; // Sample test resource plugin -public class SampleTestResourcePlugin extends Plugin implements ResourcePlugin { +public class TestResourcePlugin extends Plugin implements ResourcePlugin { public static final String SAMPLE_TEST_INDEX = ".sample_test_resource"; @@ -76,4 +76,19 @@ public void start() {} public void stop() {} } + + public static class TestResource { + public String id; + public String name; + + public TestResource() {} + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java index 7d3b2d43f3780..a157134bec8a1 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java @@ -8,6 +8,8 @@ package org.opensearch.accesscontrol.resources.fallback; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; @@ -27,13 +29,12 @@ import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.threadpool.ThreadPool; -import java.lang.reflect.Field; import java.security.AccessController; -import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -42,14 +43,14 @@ * * @opensearch.experimental */ -@SuppressWarnings("removal") public class DefaultResourceAccessControlPlugin extends Plugin implements ResourceAccessControlPlugin { private static final Logger log = LogManager.getLogger(DefaultResourceAccessControlPlugin.class); - private Client client; + private final Client client; private final ThreadPool threadPool; + private static final ObjectMapper objectMapper = new ObjectMapper(); @Inject public DefaultResourceAccessControlPlugin(Client client, ThreadPool threadPool) { @@ -79,7 +80,7 @@ public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Cla SearchHit[] searchHits = searchResponse.getHits().getHits(); while (searchHits != null && searchHits.length > 0) { - parseAndAddToDocuments(Arrays.stream(searchHits).map(SearchHit::getSourceAsMap), clazz, documents); + parseAndAddToDocuments(Arrays.stream(searchHits), clazz, documents); final SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); scrollRequest.scroll(scrollTimeout); @@ -112,26 +113,17 @@ private SearchRequest buildSearchRequest(String resourceIndex, TimeValue scrollT return searchRequest; } - private static T parse(Map sourceMap, Class clazz) { - return AccessController.doPrivileged((PrivilegedAction) () -> { - try { - final T instance = clazz.getDeclaredConstructor().newInstance(); - for (Field field : clazz.getDeclaredFields()) { - field.setAccessible(true); - Object value = sourceMap.get(field.getName()); - if (value != null) { - field.set(instance, value); - } - } - return instance; - } catch (Exception e) { - throw new OpenSearchException("Failed to parse source map into " + clazz.getName(), e); - } - }); + private static T parse(String source, Class clazz) { + try { + return AccessController.doPrivileged((PrivilegedExceptionAction) () -> objectMapper.readValue(source, clazz)); + } catch (PrivilegedActionException e) { + log.error("Error parsing source: {}", e.toString()); + throw new OpenSearchException("Error parsing source into : " + clazz.getName(), e.getException()); + } } - private static void parseAndAddToDocuments(Stream> searchHits, Class clazz, Set documents) { - searchHits.map(hit -> parse(hit, clazz)).forEach(documents::add); + private static void parseAndAddToDocuments(Stream searchHits, Class clazz, Set documents) { + searchHits.map(hit -> parse(hit.getSourceAsString(), clazz)).forEach(documents::add); } } diff --git a/server/src/main/resources/org/opensearch/bootstrap/security.policy b/server/src/main/resources/org/opensearch/bootstrap/security.policy index 5cd546adf3eda..9e794bf76ccae 100644 --- a/server/src/main/resources/org/opensearch/bootstrap/security.policy +++ b/server/src/main/resources/org/opensearch/bootstrap/security.policy @@ -48,14 +48,16 @@ grant codeBase "${codebase.opensearch}" { permission java.lang.RuntimePermission "setContextClassLoader"; // needed for SPI class loading permission java.lang.RuntimePermission "accessDeclaredMembers"; + // needed for DefaultResourceAccessPlugin + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission org.opensearch.secure_sm.ThreadContextPermission "markAsSystemContext"; permission org.opensearch.secure_sm.ThreadContextPermission "stashWithOrigin"; }; -//// Permission specific to DefaultResourceAccessControlPlugin -grant codeBase "${codebase.opensearch}" { - // ReflectPermission granted only for the DefaultResourceAccessControlPlugin to allow parsing the Resources +//// Permission specific to jackson-databind used in DefaultResourceAccessPlugin +grant codeBase "${codebase.jackson-databind}" { permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; + permission java.lang.RuntimePermission "accessDeclaredMembers"; }; //// Very special jar permissions: From 0bf9fd12984efe96a26f1a81df8d533d13ffddda Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 17:57:51 -0500 Subject: [PATCH 36/51] Completes the integrations tests Signed-off-by: Darshit Chanpura --- .../DefaultResourceAccessControlPluginIT.java | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java index 0848b9fb48f38..63d23ba9adc27 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java @@ -8,6 +8,10 @@ package org.opensearch.accesscontrol.resources.fallback; +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.client.Client; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.plugins.Plugin; @@ -19,6 +23,7 @@ import java.io.IOException; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; import static org.opensearch.accesscontrol.resources.fallback.TestResourcePlugin.SAMPLE_TEST_INDEX; @@ -27,6 +32,7 @@ import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; public class DefaultResourceAccessControlPluginIT extends OpenSearchIntegTestCase { @Override @@ -57,7 +63,7 @@ public void testGetResources() throws IOException { } } - public void testSampleResourcePluginCallsDefaultRACPlugin() throws IOException { + public void testSampleResourcePluginListResources() throws IOException { createIndex(SAMPLE_TEST_INDEX); indexSampleDocuments(); @@ -75,6 +81,60 @@ public void testSampleResourcePluginCallsDefaultRACPlugin() throws IOException { MatcherAssert.assertThat(resources, hasItem(hasProperty("id", is("2")))); } + public void testSampleResourcePluginCallsHasPermission() { + + ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); + + boolean canAccess = racPlugin.hasPermission("1", SAMPLE_TEST_INDEX, "some_scope"); + + MatcherAssert.assertThat(canAccess, is(true)); + + } + + public void testSampleResourcePluginCallsShareWith() { + + ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); + + SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of()); + SharedWithScope sharedWithScope = new SharedWithScope("some_scope", sharedWithPerScope); + ResourceSharing sharingInfo = racPlugin.shareWith("1", SAMPLE_TEST_INDEX, new ShareWith(Set.of(sharedWithScope))); + + MatcherAssert.assertThat(sharingInfo, is(nullValue())); + } + + public void testSampleResourcePluginCallsRevokeAccess() { + + ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); + + Map> entityTypes = Map.of(EntityType.USERS, Set.of("some_user")); + ResourceSharing sharingInfo = racPlugin.revokeAccess("1", SAMPLE_TEST_INDEX, entityTypes, Set.of("some_scope")); + + MatcherAssert.assertThat(sharingInfo, is(nullValue())); + } + + public void testSampleResourcePluginCallsDeleteResourceSharingRecord() { + ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.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(DefaultResourceAccessControlPlugin.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(); From 4a073f1e191c3c1e653322f70e1068f626510c7b Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 18:00:24 -0500 Subject: [PATCH 37/51] Updates SHAs Signed-off-by: Darshit Chanpura --- .../core}/licenses/jackson-annotations-2.18.2.jar.sha1 | 0 .../core}/licenses/jackson-databind-2.18.2.jar.sha1 | 0 plugins/crypto-kms/licenses/jackson-annotations-2.18.2.jar.sha1 | 1 - plugins/crypto-kms/licenses/jackson-databind-2.18.2.jar.sha1 | 1 - .../discovery-ec2/licenses/jackson-annotations-2.18.2.jar.sha1 | 1 - plugins/discovery-ec2/licenses/jackson-databind-2.18.2.jar.sha1 | 1 - .../licenses/jackson-annotations-2.18.2.jar.sha1 | 1 - .../repository-azure/licenses/jackson-databind-2.18.2.jar.sha1 | 1 - .../repository-s3/licenses/jackson-annotations-2.18.2.jar.sha1 | 1 - plugins/repository-s3/licenses/jackson-databind-2.18.2.jar.sha1 | 1 - .../licenses/jackson-annotations-2.18.2.jar.sha1 | 0 .../licenses/jackson-databind-2.18.2.jar.sha1 | 0 12 files changed, 8 deletions(-) rename {distribution/tools/upgrade-cli => libs/core}/licenses/jackson-annotations-2.18.2.jar.sha1 (100%) rename {distribution/tools/upgrade-cli => libs/core}/licenses/jackson-databind-2.18.2.jar.sha1 (100%) delete mode 100644 plugins/crypto-kms/licenses/jackson-annotations-2.18.2.jar.sha1 delete mode 100644 plugins/crypto-kms/licenses/jackson-databind-2.18.2.jar.sha1 delete mode 100644 plugins/discovery-ec2/licenses/jackson-annotations-2.18.2.jar.sha1 delete mode 100644 plugins/discovery-ec2/licenses/jackson-databind-2.18.2.jar.sha1 delete mode 100644 plugins/repository-azure/licenses/jackson-annotations-2.18.2.jar.sha1 delete mode 100644 plugins/repository-azure/licenses/jackson-databind-2.18.2.jar.sha1 delete mode 100644 plugins/repository-s3/licenses/jackson-annotations-2.18.2.jar.sha1 delete mode 100644 plugins/repository-s3/licenses/jackson-databind-2.18.2.jar.sha1 rename {modules/ingest-geoip => server}/licenses/jackson-annotations-2.18.2.jar.sha1 (100%) rename {modules/ingest-geoip => server}/licenses/jackson-databind-2.18.2.jar.sha1 (100%) diff --git a/distribution/tools/upgrade-cli/licenses/jackson-annotations-2.18.2.jar.sha1 b/libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 similarity index 100% rename from distribution/tools/upgrade-cli/licenses/jackson-annotations-2.18.2.jar.sha1 rename to libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 diff --git a/distribution/tools/upgrade-cli/licenses/jackson-databind-2.18.2.jar.sha1 b/libs/core/licenses/jackson-databind-2.18.2.jar.sha1 similarity index 100% rename from distribution/tools/upgrade-cli/licenses/jackson-databind-2.18.2.jar.sha1 rename to libs/core/licenses/jackson-databind-2.18.2.jar.sha1 diff --git a/plugins/crypto-kms/licenses/jackson-annotations-2.18.2.jar.sha1 b/plugins/crypto-kms/licenses/jackson-annotations-2.18.2.jar.sha1 deleted file mode 100644 index a06e1d5f28425..0000000000000 --- a/plugins/crypto-kms/licenses/jackson-annotations-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -985d77751ebc7fce5db115a986bc9aa82f973f4a \ No newline at end of file diff --git a/plugins/crypto-kms/licenses/jackson-databind-2.18.2.jar.sha1 b/plugins/crypto-kms/licenses/jackson-databind-2.18.2.jar.sha1 deleted file mode 100644 index eedbfff66c705..0000000000000 --- a/plugins/crypto-kms/licenses/jackson-databind-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -deef8697b92141fb6caf7aa86966cff4eec9b04f \ No newline at end of file diff --git a/plugins/discovery-ec2/licenses/jackson-annotations-2.18.2.jar.sha1 b/plugins/discovery-ec2/licenses/jackson-annotations-2.18.2.jar.sha1 deleted file mode 100644 index a06e1d5f28425..0000000000000 --- a/plugins/discovery-ec2/licenses/jackson-annotations-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -985d77751ebc7fce5db115a986bc9aa82f973f4a \ No newline at end of file diff --git a/plugins/discovery-ec2/licenses/jackson-databind-2.18.2.jar.sha1 b/plugins/discovery-ec2/licenses/jackson-databind-2.18.2.jar.sha1 deleted file mode 100644 index eedbfff66c705..0000000000000 --- a/plugins/discovery-ec2/licenses/jackson-databind-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -deef8697b92141fb6caf7aa86966cff4eec9b04f \ No newline at end of file diff --git a/plugins/repository-azure/licenses/jackson-annotations-2.18.2.jar.sha1 b/plugins/repository-azure/licenses/jackson-annotations-2.18.2.jar.sha1 deleted file mode 100644 index a06e1d5f28425..0000000000000 --- a/plugins/repository-azure/licenses/jackson-annotations-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -985d77751ebc7fce5db115a986bc9aa82f973f4a \ No newline at end of file diff --git a/plugins/repository-azure/licenses/jackson-databind-2.18.2.jar.sha1 b/plugins/repository-azure/licenses/jackson-databind-2.18.2.jar.sha1 deleted file mode 100644 index eedbfff66c705..0000000000000 --- a/plugins/repository-azure/licenses/jackson-databind-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -deef8697b92141fb6caf7aa86966cff4eec9b04f \ No newline at end of file diff --git a/plugins/repository-s3/licenses/jackson-annotations-2.18.2.jar.sha1 b/plugins/repository-s3/licenses/jackson-annotations-2.18.2.jar.sha1 deleted file mode 100644 index a06e1d5f28425..0000000000000 --- a/plugins/repository-s3/licenses/jackson-annotations-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -985d77751ebc7fce5db115a986bc9aa82f973f4a \ No newline at end of file diff --git a/plugins/repository-s3/licenses/jackson-databind-2.18.2.jar.sha1 b/plugins/repository-s3/licenses/jackson-databind-2.18.2.jar.sha1 deleted file mode 100644 index eedbfff66c705..0000000000000 --- a/plugins/repository-s3/licenses/jackson-databind-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -deef8697b92141fb6caf7aa86966cff4eec9b04f \ No newline at end of file diff --git a/modules/ingest-geoip/licenses/jackson-annotations-2.18.2.jar.sha1 b/server/licenses/jackson-annotations-2.18.2.jar.sha1 similarity index 100% rename from modules/ingest-geoip/licenses/jackson-annotations-2.18.2.jar.sha1 rename to server/licenses/jackson-annotations-2.18.2.jar.sha1 diff --git a/modules/ingest-geoip/licenses/jackson-databind-2.18.2.jar.sha1 b/server/licenses/jackson-databind-2.18.2.jar.sha1 similarity index 100% rename from modules/ingest-geoip/licenses/jackson-databind-2.18.2.jar.sha1 rename to server/licenses/jackson-databind-2.18.2.jar.sha1 From 34f160cf2f42dfee0efc1753c7d3dfc48a0b197e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 19:15:36 -0500 Subject: [PATCH 38/51] Comment and code cleanup Signed-off-by: Darshit Chanpura --- .../DefaultResourceAccessControlPluginIT.java | 4 +- .../accesscontrol/resources/CreatedBy.java | 13 ++--- .../resources/ResourceAccessScope.java | 6 +-- .../resources/ResourceService.java | 6 +-- .../resources/ResourceSharing.java | 50 +++++++++++++------ .../accesscontrol/resources/ShareWith.java | 6 ++- .../resources/SharedWithScope.java | 44 +++++++++------- .../DefaultResourceAccessControlPlugin.java | 5 +- .../resources/fallback/package-info.java | 2 +- .../accesscontrol/resources/package-info.java | 2 +- .../plugins/ResourceAccessControlPlugin.java | 25 ++++------ .../resources/ResourceServiceTests.java | 17 ++++--- .../resources/ShareWithTests.java | 42 ++++++++-------- 13 files changed, 127 insertions(+), 95 deletions(-) diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java index 63d23ba9adc27..25957a4c1add7 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java @@ -97,8 +97,8 @@ public void testSampleResourcePluginCallsShareWith() { ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); - SharedWithScope.SharedWithPerScope sharedWithPerScope = new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of()); - SharedWithScope sharedWithScope = new SharedWithScope("some_scope", sharedWithPerScope); + SharedWithScope.ScopeRecipients scopeRecipients = new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of()); + SharedWithScope sharedWithScope = new SharedWithScope("some_scope", scopeRecipients); ResourceSharing sharingInfo = racPlugin.shareWith("1", SAMPLE_TEST_INDEX, new ShareWith(Set.of(sharedWithScope))); MatcherAssert.assertThat(sharingInfo, is(nullValue())); diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java index 8482ff4c1318f..4fb3d8e21ded7 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java @@ -18,13 +18,14 @@ import java.io.IOException; /** - * This class contains information on the creator of a resource. - * Creator can either be a user or a backend_role. + * This class is used to store information about the creator of a resource. + * Creator can only be a user. * * @opensearch.experimental */ public class CreatedBy implements ToXContentFragment, NamedWriteable { + private static final String USER_FIELD = "user"; private String user; public CreatedBy(String user) { @@ -45,7 +46,7 @@ public void setUser(String user) { @Override public String toString() { - return "CreatedBy {" + "user='" + user + '\'' + '}'; + return "CreatedBy {" + USER_FIELD + "='" + user + '\'' + '}'; } @Override @@ -60,7 +61,7 @@ public void writeTo(StreamOutput out) throws IOException { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("user", user).endObject(); + return builder.startObject().field(USER_FIELD, user).endObject(); } public static CreatedBy fromXContent(XContentParser parser) throws IOException { @@ -72,14 +73,14 @@ public static CreatedBy fromXContent(XContentParser parser) throws IOException { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); } else if (token == XContentParser.Token.VALUE_STRING) { - if ("user".equals(currentFieldName)) { + if (USER_FIELD.equals(currentFieldName)) { user = parser.text(); } } } if (user == null) { - throw new IllegalArgumentException("user field is required"); + throw new IllegalArgumentException(USER_FIELD + " is required"); } return new CreatedBy(user); diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java index 46138a5e788c6..ac27945b637a5 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceAccessScope.java @@ -10,12 +10,12 @@ /** * This interface defines the two basic access scopes for resource-access. - * Each plugin must implement their own scopes and manage them + * 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"; + String READ_ONLY = "read_only"; + String READ_WRITE = "read_write"; } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java index 7ea58cf223aa5..d58ec06fd34ac 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceService.java @@ -22,7 +22,7 @@ import java.util.stream.Collectors; /** - * Resource access control for OpenSearch + * Service to get the current ResourcePlugin to perform authorization. * * @opensearch.experimental */ @@ -56,14 +56,14 @@ public ResourceService( } /** - * Gets the current ResourcePlugin to perform authorization + * Gets the ResourceAccessControlPlugin in-effect to perform authorization */ public ResourceAccessControlPlugin getResourceAccessControlPlugin() { return resourceACPlugin; } /** - * List active plugins that define resources + * Gets the list of ResourcePlugins */ public List listResourcePlugins() { return List.copyOf(resourcePlugins); diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java index 421d3a0366903..cabfa20189618 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ResourceSharing.java @@ -18,21 +18,45 @@ import java.util.Objects; /** - * A document in .resource_sharing index. - * Holds information about the resource (obtained from defining plugin's meta-data), - * the index which defines the resources, the creator of the resource, - * and the information on whom this resource is shared with. + * 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 CreatedBy + * @see 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) { @@ -168,18 +192,16 @@ public static ResourceSharing fromXContent(XContentParser parser) throws IOExcep } } - // Validate required fields - if (sourceIdx == null) { - throw new IllegalArgumentException("source_idx is required"); - } - if (resourceId == null) { - throw new IllegalArgumentException("resource_id is required"); - } - if (createdBy == null) { - throw new IllegalArgumentException("created_by is required"); - } + 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/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java index c50a973cef52d..e3f003f12728b 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/ShareWith.java @@ -20,8 +20,9 @@ import java.util.Set; /** + * * This class contains information about whom a resource is shared with and at what scope. - * Here is a sample of what this would look like: + * Example: * "share_with": { * "read_only": { * "users": [], @@ -39,6 +40,9 @@ */ 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) { diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index dae66785269a4..6b11ebdade9f0 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -20,7 +20,13 @@ import java.util.Set; /** - * Defines the scope and who this scope is shared with + * This class represents the scope at which a resource is shared with. + * Example: + * "read_only": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * } * * @opensearch.experimental */ @@ -28,24 +34,24 @@ public class SharedWithScope implements ToXContentFragment, NamedWriteable { private final String scope; - private final SharedWithPerScope sharedWithPerScope; + private final ScopeRecipients scopeRecipients; - public SharedWithScope(String scope, SharedWithPerScope sharedWithPerScope) { + public SharedWithScope(String scope, ScopeRecipients scopeRecipients) { this.scope = scope; - this.sharedWithPerScope = sharedWithPerScope; + this.scopeRecipients = scopeRecipients; } public SharedWithScope(StreamInput in) throws IOException { this.scope = in.readString(); - this.sharedWithPerScope = new SharedWithPerScope(in); + this.scopeRecipients = new ScopeRecipients(in); } public String getScope() { return scope; } - public SharedWithPerScope getSharedWithPerScope() { - return sharedWithPerScope; + public ScopeRecipients getSharedWithPerScope() { + return scopeRecipients; } @Override @@ -53,7 +59,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field(scope); builder.startObject(); - sharedWithPerScope.toXContent(builder, params); + scopeRecipients.toXContent(builder, params); return builder.endObject(); } @@ -63,14 +69,14 @@ public static SharedWithScope fromXContent(XContentParser parser) throws IOExcep parser.nextToken(); - SharedWithPerScope sharedWithPerScope = SharedWithPerScope.fromXContent(parser); + ScopeRecipients scopeRecipients = ScopeRecipients.fromXContent(parser); - return new SharedWithScope(scope, sharedWithPerScope); + return new SharedWithScope(scope, scopeRecipients); } @Override public String toString() { - return "{" + scope + ": " + sharedWithPerScope + '}'; + return "{" + scope + ": " + scopeRecipients + '}'; } @Override @@ -81,15 +87,15 @@ public String getWriteableName() { @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(scope); - out.writeNamedWriteable(sharedWithPerScope); + out.writeNamedWriteable(scopeRecipients); } /** - * This class defines who a resource is shared_with for a particular scope + * This class represents the entities with whom a resource is shared with for a given scope. * * @opensearch.experimental */ - public static class SharedWithPerScope implements ToXContentFragment, NamedWriteable { + public static class ScopeRecipients implements ToXContentFragment, NamedWriteable { private static final String USERS_FIELD = EntityType.USERS.toString(); private static final String ROLES_FIELD = EntityType.ROLES.toString(); private static final String BACKEND_ROLES_FIELD = EntityType.BACKEND_ROLES.toString(); @@ -100,13 +106,13 @@ public static class SharedWithPerScope implements ToXContentFragment, NamedWrite private Set backendRoles; - public SharedWithPerScope(Set users, Set roles, Set backendRoles) { + public ScopeRecipients(Set users, Set roles, Set backendRoles) { this.users = users; this.roles = roles; this.backendRoles = backendRoles; } - public SharedWithPerScope(StreamInput in) throws IOException { + public ScopeRecipients(StreamInput in) throws IOException { this.users = Set.of(in.readStringArray()); this.roles = Set.of(in.readStringArray()); this.backendRoles = Set.of(in.readStringArray()); @@ -161,7 +167,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws return builder; } - public static SharedWithPerScope fromXContent(XContentParser parser) throws IOException { + public static ScopeRecipients fromXContent(XContentParser parser) throws IOException { Set users = new HashSet<>(); Set roles = new HashSet<>(); Set backendRoles = new HashSet<>(); @@ -190,7 +196,7 @@ public static SharedWithPerScope fromXContent(XContentParser parser) throws IOEx } } - return new SharedWithPerScope(users, roles, backendRoles); + return new ScopeRecipients(users, roles, backendRoles); } private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, Set values) throws IOException { @@ -203,7 +209,7 @@ private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, S @Override public String getWriteableName() { - return "shared_with_per_scope"; + return "scope_recipients"; } @Override diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java index a157134bec8a1..246de3763aed6 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java @@ -40,6 +40,7 @@ /** * This plugin class defines a pass-through implementation of ResourceAccessControlPlugin. + * This plugin will come into effect when security plugin is disabled in the cluster. * * @opensearch.experimental */ @@ -59,7 +60,7 @@ public DefaultResourceAccessControlPlugin(Client client, ThreadPool threadPool) } /** - * Returns a list of all resource ids in provided systemIndex + * Returns a list of all resource ids in provided resourceIndex * @param resourceIndex index where resources are stored * * @return Set of resource ids @@ -93,7 +94,7 @@ public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Cla clearScrollRequest.addScrollId(scrollId); client.clearScroll(clearScrollRequest).actionGet(); } catch (Exception e) { - log.error("Exception: {}", e.getMessage()); + log.error("Error fetching resources : {}", e.getMessage()); return Set.of(); } log.info("Found {} documents", documents.size()); diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java index 5658a60d641d6..82c60fe3406b4 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/package-info.java @@ -7,7 +7,7 @@ */ /** - * This package defines all default implementations of Resource Sharing and Access Control which will be utilized when security is disabled + * This package defines a pass-through implementation of ResourceAccessControlPlugin. * * @opensearch.experimental */ diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java index a0dfd013dc5c3..61ccd7d879b05 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/package-info.java @@ -7,7 +7,7 @@ */ /** - * This package defines all classes required for Resource Sharing and Access Control in OpenSearch + * This package defines class required to implement resource access control in OpenSearch. * * @opensearch.experimental */ diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 7d93065d6983c..ebdff187691b9 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -16,13 +16,9 @@ import java.util.Set; /** - * This interface determines presence of security plugin in the cluster. - * If yes, security plugin will be used for resource access authorization - * User information is fetched from thread context by security plugin. - * In clusters, where security plugin is disabled these requests will be pass-through via a No-op implementation. - * There are 3 scope of sharing for a resource: Private, Restricted, Public. To learn more visit ... - * If security plugin is disabled, all resources will be considered public by default. - * Refer to the sample-resource-plugin introduced here to understand the usage of this class. + * 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 */ @@ -51,7 +47,7 @@ default boolean hasPermission(String resourceId, String resourceIndex, String sc } /** - * Adds an entity to the share-with. Resource needs to be in restricted mode. + * Adds an entity to the share-with. * Creates a resource sharing record if one doesn't exist. * @param resourceId id of the resource to be updated * @param resourceIndex index where this resource is stored @@ -63,7 +59,7 @@ default ResourceSharing shareWith(String resourceId, String resourceIndex, Share } /** - * Revokes given permission to a resource + * Revokes given scope permission to a resource * * @param resourceId if of the resource to be updated * @param resourceIndex index where this resource is stored @@ -81,8 +77,9 @@ default ResourceSharing revokeAccess( } /** - * Deletes an entry from .resource_sharing index - * @param resourceId if of the resource to be updated + * Deletes a resource sharing record + * + * @param resourceId if of the resource to delete the resource sharing record * @param resourceIndex index where this resource is stored * @return true if resource record was deleted, false otherwise */ @@ -91,13 +88,11 @@ default boolean deleteResourceSharingRecord(String resourceId, String resourceIn } /** - * TODO check if this method is needed - * Deletes all entries from .resource_sharing index where current user is the creator of the resource - * @return true if resource record was deleted, false otherwise + * Deletes all resource sharing records for current user + * @return true if resource records were deleted, false otherwise */ default boolean deleteAllResourceSharingRecordsForCurrentUser() { return false; } - // TODO: Check whether methods for bulk updates are required } diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java index 63c2cd16253b3..077236dbb270a 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java @@ -17,7 +17,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; public class ResourceServiceTests extends OpenSearchTestCase { @@ -47,7 +50,7 @@ public void testGetResourceAccessControlPluginReturnsInitializedPlugin() { ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin(); - assertEquals(mockPlugin, result); + MatcherAssert.assertThat(mockPlugin, equalTo(result)); } public void testGetResourceAccessControlPlugin_NoPlugins() { @@ -60,7 +63,7 @@ public void testGetResourceAccessControlPlugin_NoPlugins() { ResourceAccessControlPlugin result = resourceService.getResourceAccessControlPlugin(); assertNotNull(result); - assertTrue(result instanceof DefaultResourceAccessControlPlugin); + MatcherAssert.assertThat(result, instanceOf(DefaultResourceAccessControlPlugin.class)); } public void testGetResourceAccessControlPlugin_SinglePlugin() { @@ -120,7 +123,7 @@ public void testListResourcePlugins_emptyList() { List result = resourceService.listResourcePlugins(); assertNotNull(result); - assertTrue(result.isEmpty()); + MatcherAssert.assertThat(result, is(empty())); } public void testListResourcePlugins_immutability() { @@ -182,8 +185,8 @@ public void testResourceServiceWithNoAccessControlPlugin() { ResourceService resourceService = new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool); - assertTrue(resourceService.getResourceAccessControlPlugin() instanceof DefaultResourceAccessControlPlugin); - assertEquals(resourcePlugins, resourceService.listResourcePlugins()); + MatcherAssert.assertThat(resourceService.getResourceAccessControlPlugin(), instanceOf(DefaultResourceAccessControlPlugin.class)); + MatcherAssert.assertThat(resourcePlugins, equalTo(resourceService.listResourcePlugins())); } public void testResourceServiceWithNoResourceACPlugins() { @@ -207,7 +210,7 @@ public void testResourceServiceWithSingleResourceAccessControlPlugin() { ResourceService resourceService = new ResourceService(resourceACPlugins, resourcePlugins, client, threadPool); assertNotNull(resourceService); - assertEquals(mockPlugin, resourceService.getResourceAccessControlPlugin()); - assertEquals(resourcePlugins, resourceService.listResourcePlugins()); + MatcherAssert.assertThat(mockPlugin, equalTo(resourceService.getResourceAccessControlPlugin())); + MatcherAssert.assertThat(resourcePlugins, equalTo(resourceService.listResourcePlugins())); } } diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java index c1c7d48802fc8..14fbc15616d62 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java @@ -44,12 +44,12 @@ public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOExceptio SharedWithScope scope = sharedWithScopes.iterator().next(); MatcherAssert.assertThat("read_only", equalTo(scope.getScope())); - SharedWithScope.SharedWithPerScope sharedWithPerScope = scope.getSharedWithPerScope(); - assertNotNull(sharedWithPerScope); - MatcherAssert.assertThat(1, equalTo(sharedWithPerScope.getUsers().size())); - MatcherAssert.assertThat("user1", equalTo(sharedWithPerScope.getUsers().iterator().next())); - MatcherAssert.assertThat(0, equalTo(sharedWithPerScope.getRoles().size())); - MatcherAssert.assertThat(0, equalTo(sharedWithPerScope.getBackendRoles().size())); + SharedWithScope.ScopeRecipients scopeRecipients = scope.getSharedWithPerScope(); + assertNotNull(scopeRecipients); + MatcherAssert.assertThat(1, equalTo(scopeRecipients.getUsers().size())); + MatcherAssert.assertThat("user1", equalTo(scopeRecipients.getUsers().iterator().next())); + MatcherAssert.assertThat(0, equalTo(scopeRecipients.getRoles().size())); + MatcherAssert.assertThat(0, equalTo(scopeRecipients.getBackendRoles().size())); } public void testFromXContentWithEmptyInput() throws IOException { @@ -87,18 +87,18 @@ public void testFromXContentWithStartObject() throws IOException { assertNotNull(shareWith); Set scopes = shareWith.getSharedWithScopes(); - assertEquals(2, scopes.size()); + MatcherAssert.assertThat(scopes.size(), equalTo(2)); for (SharedWithScope scope : scopes) { - SharedWithScope.SharedWithPerScope perScope = scope.getSharedWithPerScope(); + SharedWithScope.ScopeRecipients perScope = scope.getSharedWithPerScope(); if (scope.getScope().equals(ResourceAccessScope.READ_ONLY)) { - assertEquals(2, perScope.getUsers().size()); - assertEquals(1, perScope.getRoles().size()); - assertEquals(1, perScope.getBackendRoles().size()); + MatcherAssert.assertThat(perScope.getUsers().size(), equalTo(2)); + MatcherAssert.assertThat(perScope.getRoles().size(), equalTo(1)); + MatcherAssert.assertThat(perScope.getBackendRoles().size(), equalTo(1)); } else if (scope.getScope().equals(ResourceAccessScope.READ_WRITE)) { - assertEquals(1, perScope.getUsers().size()); - assertEquals(2, perScope.getRoles().size()); - assertEquals(0, perScope.getBackendRoles().size()); + MatcherAssert.assertThat(perScope.getUsers().size(), equalTo(1)); + MatcherAssert.assertThat(perScope.getRoles().size(), equalTo(2)); + MatcherAssert.assertThat(perScope.getBackendRoles().size(), equalTo(0)); } } } @@ -111,11 +111,11 @@ public void testFromXContentWithUnexpectedEndOfInput() throws IOException { ShareWith result = ShareWith.fromXContent(mockParser); assertNotNull(result); - assertTrue(result.getSharedWithScopes().isEmpty()); + MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); } public void testToXContentBuildsCorrectly() throws IOException { - SharedWithScope scope = new SharedWithScope("scope1", new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of())); + SharedWithScope scope = new SharedWithScope("scope1", new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of())); Set scopes = new HashSet<>(); scopes.add(scope); @@ -146,7 +146,7 @@ public void testWriteToWithEmptySet() throws IOException { public void testWriteToWithIOException() throws IOException { Set set = new HashSet<>(); - set.add(new SharedWithScope("test", new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of()))); + set.add(new SharedWithScope("test", new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of()))); ShareWith shareWith = new ShareWith(set); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -158,7 +158,7 @@ public void testWriteToWithIOException() throws IOException { public void testWriteToWithLargeSet() throws IOException { Set largeSet = new HashSet<>(); for (int i = 0; i < 10000; i++) { - largeSet.add(new SharedWithScope("scope" + i, new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of()))); + largeSet.add(new SharedWithScope("scope" + i, new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of()))); } ShareWith shareWith = new ShareWith(largeSet); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -177,7 +177,7 @@ public void test_fromXContent_emptyObject() throws IOException { ShareWith shareWith = ShareWith.fromXContent(parser); - assertTrue(shareWith.getSharedWithScopes().isEmpty()); + MatcherAssert.assertThat(shareWith.getSharedWithScopes(), is(empty())); } public void test_writeSharedWithScopesToStream() throws IOException { @@ -185,10 +185,10 @@ public void test_writeSharedWithScopesToStream() throws IOException { Set sharedWithScopes = new HashSet<>(); sharedWithScopes.add( - new SharedWithScope(ResourceAccessScope.READ_ONLY, new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of())) + new SharedWithScope(ResourceAccessScope.READ_ONLY, new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of())) ); sharedWithScopes.add( - new SharedWithScope(ResourceAccessScope.READ_WRITE, new SharedWithScope.SharedWithPerScope(Set.of(), Set.of(), Set.of())) + new SharedWithScope(ResourceAccessScope.READ_WRITE, new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of())) ); ShareWith shareWith = new ShareWith(sharedWithScopes); From 1df8e11a91135508c2709e541268f28a17dfedf5 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 18 Dec 2024 19:17:46 -0500 Subject: [PATCH 39/51] Fixes tests Signed-off-by: Darshit Chanpura --- .../opensearch/accesscontrol/resources/CreatedByTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java index 52b0bfd01c7fc..4ea1e52974c25 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java @@ -85,7 +85,7 @@ public void testFromXContentThrowsExceptionWhenUserFieldIsMissing() throws IOExc exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); } - MatcherAssert.assertThat("user field is required", equalTo(exception.getMessage())); + MatcherAssert.assertThat("user is required", equalTo(exception.getMessage())); } public void testFromXContentWithEmptyInput() throws IOException { @@ -120,7 +120,7 @@ public void testFromXContentWithMissingUser() throws IOException { exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); } - MatcherAssert.assertThat("user field is required", equalTo(exception.getMessage())); + MatcherAssert.assertThat("user is required", equalTo(exception.getMessage())); } public void testFromXContentWithMissingUserField() throws IOException { @@ -266,7 +266,7 @@ public void test_fromXContent_missingUserField() throws IOException { exception = assertThrows(IllegalArgumentException.class, () -> { CreatedBy.fromXContent(parser); }); } - MatcherAssert.assertThat("user field is required", equalTo(exception.getMessage())); + MatcherAssert.assertThat("user is required", equalTo(exception.getMessage())); } public void test_toXContent_serializesCorrectly() throws IOException { From 77c56be6c26d370be052967ce2894002b0d8ba56 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 19 Dec 2024 21:39:57 -0500 Subject: [PATCH 40/51] Adds integration tests for non-default RAC plugin Signed-off-by: Darshit Chanpura --- ...nDefaultResourceAccessControlPluginIT.java | 32 +++++++++++++++++++ .../DefaultResourceAccessControlPluginIT.java | 3 +- .../resources/testplugins/TestRACPlugin.java | 14 ++++++++ .../TestResourcePlugin.java | 2 +- 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/NonDefaultResourceAccessControlPluginIT.java create mode 100644 server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestRACPlugin.java rename server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/{fallback => testplugins}/TestResourcePlugin.java (97%) diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/NonDefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/NonDefaultResourceAccessControlPluginIT.java new file mode 100644 index 0000000000000..a69935e753d4f --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/NonDefaultResourceAccessControlPluginIT.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.accesscontrol.resources; + +import org.opensearch.accesscontrol.resources.testplugins.TestRACPlugin; +import org.opensearch.accesscontrol.resources.testplugins.TestResourcePlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; +import org.opensearch.test.OpenSearchIntegTestCase; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; + +import java.util.Collection; +import java.util.List; + +public class NonDefaultResourceAccessControlPluginIT extends OpenSearchIntegTestCase { + @Override + protected Collection> nodePlugins() { + return List.of(TestResourcePlugin.class, TestRACPlugin.class); + } + + public void testSampleResourcePluginCallsTestRACPluginToManageResourceAccess() { + ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); + MatcherAssert.assertThat(racPlugin.getClass(), Matchers.is(TestRACPlugin.class)); + } +} diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java index 25957a4c1add7..9f2b4a5589323 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java @@ -12,6 +12,7 @@ import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; import org.opensearch.accesscontrol.resources.SharedWithScope; +import org.opensearch.accesscontrol.resources.testplugins.TestResourcePlugin; import org.opensearch.client.Client; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.plugins.Plugin; @@ -26,7 +27,7 @@ import java.util.Map; import java.util.Set; -import static org.opensearch.accesscontrol.resources.fallback.TestResourcePlugin.SAMPLE_TEST_INDEX; +import static org.opensearch.accesscontrol.resources.testplugins.TestResourcePlugin.SAMPLE_TEST_INDEX; import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasProperty; diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestRACPlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestRACPlugin.java new file mode 100644 index 0000000000000..e9cde63cc6aa5 --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestRACPlugin.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. + */ + +package org.opensearch.accesscontrol.resources.testplugins; + +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.ResourceAccessControlPlugin; + +public class TestRACPlugin extends Plugin implements ResourceAccessControlPlugin {} diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/TestResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java similarity index 97% rename from server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/TestResourcePlugin.java rename to server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java index bf2e0859f8f8b..f340f6d56e221 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/TestResourcePlugin.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.accesscontrol.resources.fallback; +package org.opensearch.accesscontrol.resources.testplugins; import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.common.inject.Inject; From bb584bb66adf31a3c4e64b8c11877e9d714748d5 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 19 Dec 2024 22:05:03 -0500 Subject: [PATCH 41/51] Removes unused dependency licenses Signed-off-by: Darshit Chanpura --- .../upgrade-cli/licenses/jackson-LICENSE | 8 -------- .../tools/upgrade-cli/licenses/jackson-NOTICE | 20 ------------------- libs/x-content/build.gradle | 6 ------ .../licenses/jackson-annotations-LICENSE | 8 -------- .../licenses/jackson-annotations-NOTICE | 20 ------------------- .../licenses/jackson-databind-LICENSE | 8 -------- .../licenses/jackson-databind-NOTICE | 20 ------------------- plugins/crypto-kms/licenses/jackson-LICENSE | 8 -------- plugins/crypto-kms/licenses/jackson-NOTICE | 20 ------------------- .../discovery-ec2/licenses/jackson-LICENSE | 8 -------- plugins/discovery-ec2/licenses/jackson-NOTICE | 20 ------------------- .../repository-s3/licenses/jackson-LICENSE | 8 -------- plugins/repository-s3/licenses/jackson-NOTICE | 20 ------------------- 13 files changed, 174 deletions(-) delete mode 100644 distribution/tools/upgrade-cli/licenses/jackson-LICENSE delete mode 100644 distribution/tools/upgrade-cli/licenses/jackson-NOTICE delete mode 100644 modules/ingest-geoip/licenses/jackson-annotations-LICENSE delete mode 100644 modules/ingest-geoip/licenses/jackson-annotations-NOTICE delete mode 100644 modules/ingest-geoip/licenses/jackson-databind-LICENSE delete mode 100644 modules/ingest-geoip/licenses/jackson-databind-NOTICE delete mode 100644 plugins/crypto-kms/licenses/jackson-LICENSE delete mode 100644 plugins/crypto-kms/licenses/jackson-NOTICE delete mode 100644 plugins/discovery-ec2/licenses/jackson-LICENSE delete mode 100644 plugins/discovery-ec2/licenses/jackson-NOTICE delete mode 100644 plugins/repository-s3/licenses/jackson-LICENSE delete mode 100644 plugins/repository-s3/licenses/jackson-NOTICE diff --git a/distribution/tools/upgrade-cli/licenses/jackson-LICENSE b/distribution/tools/upgrade-cli/licenses/jackson-LICENSE deleted file mode 100644 index f5f45d26a49d6..0000000000000 --- a/distribution/tools/upgrade-cli/licenses/jackson-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 diff --git a/distribution/tools/upgrade-cli/licenses/jackson-NOTICE b/distribution/tools/upgrade-cli/licenses/jackson-NOTICE deleted file mode 100644 index 4c976b7b4cc58..0000000000000 --- a/distribution/tools/upgrade-cli/licenses/jackson-NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -# Jackson JSON processor - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. diff --git a/libs/x-content/build.gradle b/libs/x-content/build.gradle index dab07c5af336a..3be13dc2926fd 100644 --- a/libs/x-content/build.gradle +++ b/libs/x-content/build.gradle @@ -58,12 +58,6 @@ tasks.named('forbiddenApisMain').configure { replaceSignatureFiles 'jdk-signatures' } -thirdPartyAudit.ignoreMissingClasses( - // from com.fasterxml.jackson.dataformat.yaml.YAMLMapper (jackson-dataformat-yaml) - 'com.fasterxml.jackson.databind.ObjectMapper', - 'com.fasterxml.jackson.databind.cfg.MapperBuilder' -) - tasks.named("dependencyLicenses").configure { mapping from: /jackson-.*/, to: 'jackson' } diff --git a/modules/ingest-geoip/licenses/jackson-annotations-LICENSE b/modules/ingest-geoip/licenses/jackson-annotations-LICENSE deleted file mode 100644 index f5f45d26a49d6..0000000000000 --- a/modules/ingest-geoip/licenses/jackson-annotations-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 diff --git a/modules/ingest-geoip/licenses/jackson-annotations-NOTICE b/modules/ingest-geoip/licenses/jackson-annotations-NOTICE deleted file mode 100644 index 4c976b7b4cc58..0000000000000 --- a/modules/ingest-geoip/licenses/jackson-annotations-NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -# Jackson JSON processor - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. diff --git a/modules/ingest-geoip/licenses/jackson-databind-LICENSE b/modules/ingest-geoip/licenses/jackson-databind-LICENSE deleted file mode 100644 index f5f45d26a49d6..0000000000000 --- a/modules/ingest-geoip/licenses/jackson-databind-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 diff --git a/modules/ingest-geoip/licenses/jackson-databind-NOTICE b/modules/ingest-geoip/licenses/jackson-databind-NOTICE deleted file mode 100644 index 4c976b7b4cc58..0000000000000 --- a/modules/ingest-geoip/licenses/jackson-databind-NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -# Jackson JSON processor - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. diff --git a/plugins/crypto-kms/licenses/jackson-LICENSE b/plugins/crypto-kms/licenses/jackson-LICENSE deleted file mode 100644 index f5f45d26a49d6..0000000000000 --- a/plugins/crypto-kms/licenses/jackson-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 diff --git a/plugins/crypto-kms/licenses/jackson-NOTICE b/plugins/crypto-kms/licenses/jackson-NOTICE deleted file mode 100644 index 4c976b7b4cc58..0000000000000 --- a/plugins/crypto-kms/licenses/jackson-NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -# Jackson JSON processor - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. diff --git a/plugins/discovery-ec2/licenses/jackson-LICENSE b/plugins/discovery-ec2/licenses/jackson-LICENSE deleted file mode 100644 index f5f45d26a49d6..0000000000000 --- a/plugins/discovery-ec2/licenses/jackson-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 diff --git a/plugins/discovery-ec2/licenses/jackson-NOTICE b/plugins/discovery-ec2/licenses/jackson-NOTICE deleted file mode 100644 index 4c976b7b4cc58..0000000000000 --- a/plugins/discovery-ec2/licenses/jackson-NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -# Jackson JSON processor - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. diff --git a/plugins/repository-s3/licenses/jackson-LICENSE b/plugins/repository-s3/licenses/jackson-LICENSE deleted file mode 100644 index f5f45d26a49d6..0000000000000 --- a/plugins/repository-s3/licenses/jackson-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -This copy of Jackson JSON processor streaming parser/generator is licensed under the -Apache (Software) License, version 2.0 ("the License"). -See the License for details about distribution rights, and the -specific rights regarding derivate works. - -You may obtain a copy of the License at: - -http://www.apache.org/licenses/LICENSE-2.0 diff --git a/plugins/repository-s3/licenses/jackson-NOTICE b/plugins/repository-s3/licenses/jackson-NOTICE deleted file mode 100644 index 4c976b7b4cc58..0000000000000 --- a/plugins/repository-s3/licenses/jackson-NOTICE +++ /dev/null @@ -1,20 +0,0 @@ -# Jackson JSON processor - -Jackson is a high-performance, Free/Open Source JSON processing library. -It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has -been in development since 2007. -It is currently developed by a community of developers, as well as supported -commercially by FasterXML.com. - -## Licensing - -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). - -## Credits - -A list of contributors may be found from CREDITS file, which is included -in some artifacts (usually source distributions); but is always available -from the source code management (SCM) system project uses. From 448307b0229f06234b998cd6910bd2c0da96d1da Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 19 Dec 2024 22:08:12 -0500 Subject: [PATCH 42/51] Adds missing license headers Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/CreatedByTests.java | 8 ++++++++ .../accesscontrol/resources/ResourceServiceTests.java | 8 ++++++++ .../accesscontrol/resources/ShareWithTests.java | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java index 4ea1e52974c25..ca63807bb2d4f 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.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.accesscontrol.resources; import org.opensearch.common.io.stream.BytesStreamOutput; diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java index 077236dbb270a..48e08806ea2da 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ResourceServiceTests.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.accesscontrol.resources; import org.opensearch.OpenSearchException; diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java index 14fbc15616d62..6458fe6a08eac 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.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.accesscontrol.resources; import org.opensearch.common.xcontent.XContentFactory; From d9f52629e6b146e94789dc974273f100a517ea7c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 12:15:20 -0500 Subject: [PATCH 43/51] Adds type bounding to Resource Signed-off-by: Darshit Chanpura --- .../resources/testplugins/TestResourcePlugin.java | 3 ++- .../accesscontrol/resources/Resource.java | 14 ++++++++++++++ .../DefaultResourceAccessControlPlugin.java | 3 ++- .../plugins/ResourceAccessControlPlugin.java | 5 +++-- 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java index f340f6d56e221..a198d9e78f2d9 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java @@ -8,6 +8,7 @@ package org.opensearch.accesscontrol.resources.testplugins; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.accesscontrol.resources.ResourceService; import org.opensearch.common.inject.Inject; import org.opensearch.common.lifecycle.Lifecycle; @@ -77,7 +78,7 @@ public void stop() {} } - public static class TestResource { + public static class TestResource implements Resource { public String id; public String name; diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java new file mode 100644 index 0000000000000..376aceed80548 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.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. + */ + +package org.opensearch.accesscontrol.resources; + +/** + * Marker interface for all resources + */ +public interface Resource {} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java index 246de3763aed6..2efc519e05852 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPlugin.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.action.search.ClearScrollRequest; import org.opensearch.action.search.SearchRequest; import org.opensearch.action.search.SearchResponse; @@ -66,7 +67,7 @@ public DefaultResourceAccessControlPlugin(Client client, ThreadPool threadPool) * @return Set of resource ids */ @Override - public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { + public Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { final Set documents = new HashSet<>(); final TimeValue scrollTimeout = TimeValue.timeValueMinutes(1); String scrollId; diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index ebdff187691b9..663f786323b6b 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -9,6 +9,7 @@ package org.opensearch.plugins; import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; @@ -27,10 +28,10 @@ public interface ResourceAccessControlPlugin { /** * Returns all accessible resources for current user for a given plugin index. * @param resourceIndex index where the resource exists - * @param clazz class of the resource. Required to parse the resource object retrieved from resourceIndex + * @param clazz class of the resource. Required to parse the resource object retrieved from resourceIndex. Must be a type of {@link Resource} * @return set of {@link ResourceSharing} items accessible by current user. */ - default Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { + default Set getAccessibleResourcesForCurrentUser(String resourceIndex, Class clazz) { return Set.of(); } From 1aff3500a44ef50d31dbf49e4bca91605f80d45c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 12:23:09 -0500 Subject: [PATCH 44/51] Updates Resource to be a serializable Signed-off-by: Darshit Chanpura --- .../testplugins/TestResourcePlugin.java | 18 ++++++++++++++++++ .../accesscontrol/resources/Resource.java | 5 ++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java index a198d9e78f2d9..da790bb4ea4e1 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java @@ -14,9 +14,12 @@ import org.opensearch.common.lifecycle.Lifecycle; import org.opensearch.common.lifecycle.LifecycleComponent; import org.opensearch.common.lifecycle.LifecycleListener; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.plugins.Plugin; import org.opensearch.plugins.ResourcePlugin; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -91,5 +94,20 @@ public String getId() { public String getName() { return name; } + + @Override + public String getWriteableName() { + return "test_resource"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return null; + } } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java index 376aceed80548..5cc317988958d 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java @@ -8,7 +8,10 @@ package org.opensearch.accesscontrol.resources; +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.xcontent.ToXContentFragment; + /** * Marker interface for all resources */ -public interface Resource {} +public interface Resource extends NamedWriteable, ToXContentFragment {} From f90b16576734736da865bd79036ee95238b62aa6 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 20 Dec 2024 12:37:01 -0500 Subject: [PATCH 45/51] Adds getResourceName as API contract for Resource Signed-off-by: Darshit Chanpura --- .../resources/testplugins/TestResourcePlugin.java | 9 ++++++--- .../opensearch/accesscontrol/resources/Resource.java | 10 +++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java index da790bb4ea4e1..7c0214fbcc000 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/testplugins/TestResourcePlugin.java @@ -101,13 +101,16 @@ public String getWriteableName() { } @Override - public void writeTo(StreamOutput out) throws IOException { - - } + public void writeTo(StreamOutput out) {} @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return null; } + + @Override + public String getResourceName() { + return name; + } } } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java index 5cc317988958d..97f031515b526 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/Resource.java @@ -14,4 +14,12 @@ /** * Marker interface for all resources */ -public interface Resource extends NamedWriteable, ToXContentFragment {} +public interface Resource extends NamedWriteable, ToXContentFragment { + /** + * Get the resource name + * @return resource name + */ + String getResourceName(); + + // TODO: Next iteration, check if getResourceType() should be implemented +} From ce9d5ec237350374b04bfe7565ddf01eec065ce4 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 16:20:13 -0500 Subject: [PATCH 46/51] Removes notion of users, roles and backend_roles Signed-off-by: Darshit Chanpura --- .../DefaultResourceAccessControlPluginIT.java | 9 +- .../accesscontrol/resources/CreatedBy.java | 68 +--- .../accesscontrol/resources/EntityType.java | 48 --- .../resources/RecipientType.java | 32 ++ .../resources/RecipientTypeRegistry.java | 33 ++ .../resources/SharedWithScope.java | 134 +++----- .../plugins/ResourceAccessControlPlugin.java | 4 +- .../resources/CreatedByTests.java | 304 ------------------ .../resources/ShareWithTests.java | 88 +++-- 9 files changed, 181 insertions(+), 539 deletions(-) delete mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/RecipientType.java create mode 100644 server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java delete mode 100644 server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java diff --git a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java index 9f2b4a5589323..9885df3d9d42e 100644 --- a/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/accesscontrol/resources/fallback/DefaultResourceAccessControlPluginIT.java @@ -8,10 +8,8 @@ package org.opensearch.accesscontrol.resources.fallback; -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.accesscontrol.resources.testplugins.TestResourcePlugin; import org.opensearch.client.Client; import org.opensearch.core.xcontent.XContentBuilder; @@ -98,9 +96,7 @@ public void testSampleResourcePluginCallsShareWith() { ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); - SharedWithScope.ScopeRecipients scopeRecipients = new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of()); - SharedWithScope sharedWithScope = new SharedWithScope("some_scope", scopeRecipients); - ResourceSharing sharingInfo = racPlugin.shareWith("1", SAMPLE_TEST_INDEX, new ShareWith(Set.of(sharedWithScope))); + ResourceSharing sharingInfo = racPlugin.shareWith("1", SAMPLE_TEST_INDEX, new ShareWith(Set.of())); MatcherAssert.assertThat(sharingInfo, is(nullValue())); } @@ -110,8 +106,7 @@ public void testSampleResourcePluginCallsRevokeAccess() { ResourceAccessControlPlugin racPlugin = TestResourcePlugin.GuiceHolder.getResourceService().getResourceAccessControlPlugin(); MatcherAssert.assertThat(racPlugin.getClass(), is(DefaultResourceAccessControlPlugin.class)); - Map> entityTypes = Map.of(EntityType.USERS, Set.of("some_user")); - ResourceSharing sharingInfo = racPlugin.revokeAccess("1", SAMPLE_TEST_INDEX, entityTypes, Set.of("some_scope")); + ResourceSharing sharingInfo = racPlugin.revokeAccess("1", SAMPLE_TEST_INDEX, Map.of(), Set.of("some_scope")); MatcherAssert.assertThat(sharingInfo, is(nullValue())); } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java index 4fb3d8e21ded7..19b71fea5271f 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java @@ -9,81 +9,21 @@ package org.opensearch.accesscontrol.resources; 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; import java.io.IOException; /** * This class is used to store information about the creator of a resource. - * Creator can only be a user. + * Concrete implementation will be provided by security plugin * * @opensearch.experimental */ -public class CreatedBy implements ToXContentFragment, NamedWriteable { - - private static final String USER_FIELD = "user"; - private String user; - - public CreatedBy(String user) { - this.user = user; - } - - public CreatedBy(StreamInput in) throws IOException { - this(in.readString()); - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - @Override - public String toString() { - return "CreatedBy {" + USER_FIELD + "='" + user + '\'' + '}'; - } - - @Override - public String getWriteableName() { - return "created_by"; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(user); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field(USER_FIELD, user).endObject(); - } +public abstract class CreatedBy implements ToXContentFragment, NamedWriteable { + // Non-default implementation provided by security plugin public static CreatedBy fromXContent(XContentParser parser) throws IOException { - String user = 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 if (token == XContentParser.Token.VALUE_STRING) { - if (USER_FIELD.equals(currentFieldName)) { - user = parser.text(); - } - } - } - - if (user == null) { - throw new IllegalArgumentException(USER_FIELD + " is required"); - } - - return new CreatedBy(user); + return null; } - } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java b/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java deleted file mode 100644 index 070585562e4e6..0000000000000 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/EntityType.java +++ /dev/null @@ -1,48 +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.accesscontrol.resources; - -import java.util.Arrays; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * This enum contains the type of entities a resource can be shared with. - * - * @opensearch.experimental - */ -public enum EntityType { - - USERS("users"), - ROLES("roles"), - BACKEND_ROLES("backend_roles"); - - private static final Map VALUE_MAP = Arrays.stream(values()) - .collect(Collectors.toMap(EntityType::toString, Function.identity())); - - private final String value; - - EntityType(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - - public static EntityType fromValue(String value) { - EntityType type = VALUE_MAP.get(value); - if (type == null) { - throw new IllegalArgumentException("No enum constant with value: " + value); - } - return type; - } -} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientType.java b/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientType.java new file mode 100644 index 0000000000000..944e5d0e06106 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/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.accesscontrol.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/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java b/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java new file mode 100644 index 0000000000000..9846933c236e7 --- /dev/null +++ b/server/src/main/java/org/opensearch/accesscontrol/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.accesscontrol.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.toUpperCase(), recipientType); + } + + public static RecipientType fromValue(String value) { + RecipientType type = REGISTRY.get(value.toUpperCase()); + if (type == null) { + throw new IllegalArgumentException("Unknown RecipientType: " + value); + } + return type; + } +} diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index 6b11ebdade9f0..f1b2fff5dfba9 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -16,7 +16,9 @@ import org.opensearch.core.xcontent.XContentParser; import java.io.IOException; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; /** @@ -27,6 +29,7 @@ * "roles": [], * "backend_roles": [] * } + * where "users", "roles" and "backend_roles" are the recipient entities * * @opensearch.experimental */ @@ -96,127 +99,70 @@ public void writeTo(StreamOutput out) throws IOException { * @opensearch.experimental */ public static class ScopeRecipients implements ToXContentFragment, NamedWriteable { - private static final String USERS_FIELD = EntityType.USERS.toString(); - private static final String ROLES_FIELD = EntityType.ROLES.toString(); - private static final String BACKEND_ROLES_FIELD = EntityType.BACKEND_ROLES.toString(); - private Set users; + private final Map> recipients; - private Set roles; - - private Set backendRoles; - - public ScopeRecipients(Set users, Set roles, Set backendRoles) { - this.users = users; - this.roles = roles; - this.backendRoles = backendRoles; + public ScopeRecipients(Map> recipients) { + this.recipients = recipients; } public ScopeRecipients(StreamInput in) throws IOException { - this.users = Set.of(in.readStringArray()); - this.roles = Set.of(in.readStringArray()); - this.backendRoles = Set.of(in.readStringArray()); - } - - public Set getUsers() { - return users; - } - - public void setUsers(Set users) { - this.users = users; - } - - public Set getRoles() { - return roles; + this.recipients = in.readMap( + key -> RecipientTypeRegistry.fromValue(key.readString()), + input -> input.readSet(StreamInput::readString) + ); } - public void setRoles(Set roles) { - this.roles = roles; - } - - public Set getBackendRoles() { - return backendRoles; - } - - public void setBackendRoles(Set backendRoles) { - this.backendRoles = backendRoles; + public Map> getRecipients() { + return recipients; } @Override - public String toString() { - return "{" - + USERS_FIELD - + "=" - + users - + ", " - + ROLES_FIELD - + "=" - + roles - + ", " - + BACKEND_ROLES_FIELD - + "=" - + backendRoles - + '}'; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - writeFieldOrEmptyArray(builder, USERS_FIELD, users); - writeFieldOrEmptyArray(builder, ROLES_FIELD, roles); - writeFieldOrEmptyArray(builder, BACKEND_ROLES_FIELD, backendRoles); - return builder; + public String getWriteableName() { + return "scope_recipients"; } public static ScopeRecipients fromXContent(XContentParser parser) throws IOException { - Set users = new HashSet<>(); - Set roles = new HashSet<>(); - Set backendRoles = new HashSet<>(); + Map> recipients = new HashMap<>(); XContentParser.Token token; - String currentFieldName = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.START_ARRAY) { - if (USERS_FIELD.equals(currentFieldName)) { - while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - users.add(parser.text()); - } - } else if (ROLES_FIELD.equals(currentFieldName)) { - while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - roles.add(parser.text()); - } - } else if (BACKEND_ROLES_FIELD.equals(currentFieldName)) { - while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - backendRoles.add(parser.text()); - } - } else { - parser.skipChildren(); + String fieldName = parser.currentName(); + RecipientType recipientType = RecipientTypeRegistry.fromValue(fieldName.toUpperCase()); + + parser.nextToken(); + Set values = new HashSet<>(); + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + values.add(parser.text()); } + recipients.put(recipientType, values); } } - return new ScopeRecipients(users, roles, backendRoles); - } - - private void writeFieldOrEmptyArray(XContentBuilder builder, String fieldName, Set values) throws IOException { - if (values != null) { - builder.field(fieldName, values); - } else { - builder.array(fieldName); - } + return new ScopeRecipients(recipients); } @Override - public String getWriteableName() { - return "scope_recipients"; + 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 void writeTo(StreamOutput out) throws IOException { - out.writeStringArray(users.toArray(new String[0])); - out.writeStringArray(roles.toArray(new String[0])); - out.writeStringArray(backendRoles.toArray(new String[0])); + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (recipients.isEmpty()) { + return builder; + } + builder.startObject(); + for (Map.Entry> entry : recipients.entrySet()) { + builder.array(entry.getKey().getType(), entry.getValue().toArray()); + } + builder.endObject(); + return builder; } } } diff --git a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java index 663f786323b6b..d13ba9787cdea 100644 --- a/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/ResourceAccessControlPlugin.java @@ -8,7 +8,7 @@ package org.opensearch.plugins; -import org.opensearch.accesscontrol.resources.EntityType; +import org.opensearch.accesscontrol.resources.RecipientType; import org.opensearch.accesscontrol.resources.Resource; import org.opensearch.accesscontrol.resources.ResourceSharing; import org.opensearch.accesscontrol.resources.ShareWith; @@ -71,7 +71,7 @@ default ResourceSharing shareWith(String resourceId, String resourceIndex, Share default ResourceSharing revokeAccess( String resourceId, String resourceIndex, - Map> revokeAccess, + Map> revokeAccess, Set scopes ) { return null; diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java deleted file mode 100644 index ca63807bb2d4f..0000000000000 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java +++ /dev/null @@ -1,304 +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.accesscontrol.resources; - -import org.opensearch.common.io.stream.BytesStreamOutput; -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.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 org.hamcrest.MatcherAssert; - -import java.io.IOException; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class CreatedByTests extends OpenSearchTestCase { - - public void testCreatedByConstructorWithValidUser() { - String expectedUser = "testUser"; - CreatedBy createdBy = new CreatedBy(expectedUser); - - MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getUser()))); - } - - public void testCreatedByFromStreamInput() throws IOException { - String expectedUser = "testUser"; - - try (BytesStreamOutput out = new BytesStreamOutput()) { - out.writeString(expectedUser); - - StreamInput in = out.bytes().streamInput(); - - CreatedBy createdBy = new CreatedBy(in); - - MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getUser()))); - } - } - - 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(""); - MatcherAssert.assertThat("", equalTo(createdBy.getUser())); - } - - 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(longUsername); - MatcherAssert.assertThat(longUsername, equalTo(createdBy.getUser())); - } - - public void testCreatedByWithUnicodeCharacters() { - String unicodeUsername = "用户こんにちは"; - CreatedBy createdBy = new CreatedBy(unicodeUsername); - MatcherAssert.assertThat(unicodeUsername, equalTo(createdBy.getUser())); - } - - public void testFromXContentThrowsExceptionWhenUserFieldIsMissing() throws IOException { - String json = "{}"; - IllegalArgumentException exception; - try (XContentParser parser = createParser(XContentType.JSON.xContent(), json)) { - - exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); - } - - MatcherAssert.assertThat("user 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 testFromXContentWithMissingUser() throws IOException { - String json = "{}"; - IllegalArgumentException exception; - try (XContentParser parser = createParser(JsonXContent.jsonXContent, json)) { - parser.nextToken(); // Move to the start object token - - exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); - } - - MatcherAssert.assertThat("user is required", equalTo(exception.getMessage())); - } - - public void testFromXContentWithMissingUserField() throws IOException { - String jsonWithoutUser = "{\"someOtherField\": \"value\"}"; - try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithoutUser)) { - - assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); - } - } - - 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 = XContentType.JSON.xContent().createParser(xContentRegistry(), null, json); - - CreatedBy createdBy = CreatedBy.fromXContent(parser); - - assertNotNull(createdBy); - MatcherAssert.assertThat("testUser", equalTo(createdBy.getUser())); - } - - public void testGetUserReturnsCorrectValue() { - String expectedUser = "testUser"; - CreatedBy createdBy = new CreatedBy(expectedUser); - - String actualUser = createdBy.getUser(); - - MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); - } - - public void testGetUserWithNullString() { - - CreatedBy createdBy = new CreatedBy((String) null); - assertNull("getUser should return null when initialized with null", createdBy.getUser()); - } - - public void testGetWriteableNameReturnsCorrectString() { - CreatedBy createdBy = new CreatedBy("testUser"); - MatcherAssert.assertThat("created_by", equalTo(createdBy.getWriteableName())); - } - - public void testSetUserWithEmptyString() { - CreatedBy createdBy = new CreatedBy("initialUser"); - createdBy.setUser(""); - MatcherAssert.assertThat("", equalTo(createdBy.getUser())); - } - - public void testToStringWithEmptyUser() { - CreatedBy createdBy = new CreatedBy(""); - String result = createdBy.toString(); - MatcherAssert.assertThat("CreatedBy {user=''}", equalTo(result)); - } - - public void testToStringWithNullUser() { - CreatedBy createdBy = new CreatedBy((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(longUserName); - String result = createdBy.toString(); - assertTrue(result.startsWith("CreatedBy {user='")); - assertTrue(result.endsWith("'}")); - MatcherAssert.assertThat(1019, equalTo(result.length())); - } - - public void testToXContentWithEmptyUser() throws IOException { - CreatedBy createdBy = new CreatedBy(""); - 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("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(longUserName); - BytesStreamOutput out = new BytesStreamOutput(); - createdBy.writeTo(out); - assertTrue(out.size() > 65536); - } - - public void test_createdByToStringReturnsCorrectFormat() { - String testUser = "testUser"; - CreatedBy createdBy = new CreatedBy(testUser); - - String expected = "CreatedBy {user='" + testUser + "'}"; - String actual = createdBy.toString(); - - MatcherAssert.assertThat(expected, equalTo(actual)); - } - - public void test_fromXContent_missingUserField() throws IOException { - String json = "{}"; - IllegalArgumentException exception; - try (XContentParser parser = createParser(XContentType.JSON.xContent(), json)) { - parser.nextToken(); // Move to the start object token - - exception = assertThrows(IllegalArgumentException.class, () -> { CreatedBy.fromXContent(parser); }); - } - - MatcherAssert.assertThat("user is required", equalTo(exception.getMessage())); - } - - public void test_toXContent_serializesCorrectly() throws IOException { - String expectedUser = "testUser"; - CreatedBy createdBy = new CreatedBy(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(expectedUser); - - BytesStreamOutput out = new BytesStreamOutput(); - createdBy.writeTo(out); - - StreamInput in = out.bytes().streamInput(); - String actualUser = in.readString(); - - MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); - } - -} diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java index 6458fe6a08eac..deb4fa8fc15b0 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java @@ -16,14 +16,17 @@ import org.opensearch.core.xcontent.XContentParser; import org.opensearch.test.OpenSearchTestCase; import org.hamcrest.MatcherAssert; +import org.junit.Before; import java.io.IOException; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; 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; @@ -36,6 +39,11 @@ 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); @@ -54,10 +62,14 @@ public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOExceptio SharedWithScope.ScopeRecipients scopeRecipients = scope.getSharedWithPerScope(); assertNotNull(scopeRecipients); - MatcherAssert.assertThat(1, equalTo(scopeRecipients.getUsers().size())); - MatcherAssert.assertThat("user1", equalTo(scopeRecipients.getUsers().iterator().next())); - MatcherAssert.assertThat(0, equalTo(scopeRecipients.getRoles().size())); - MatcherAssert.assertThat(0, equalTo(scopeRecipients.getBackendRoles().size())); + 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 { @@ -99,14 +111,33 @@ 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)) { - MatcherAssert.assertThat(perScope.getUsers().size(), equalTo(2)); - MatcherAssert.assertThat(perScope.getRoles().size(), equalTo(1)); - MatcherAssert.assertThat(perScope.getBackendRoles().size(), equalTo(1)); + 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(perScope.getUsers().size(), equalTo(1)); - MatcherAssert.assertThat(perScope.getRoles().size(), equalTo(2)); - MatcherAssert.assertThat(perScope.getBackendRoles().size(), equalTo(0)); + 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) + ); } } } @@ -123,7 +154,7 @@ public void testFromXContentWithUnexpectedEndOfInput() throws IOException { } public void testToXContentBuildsCorrectly() throws IOException { - SharedWithScope scope = new SharedWithScope("scope1", new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of())); + SharedWithScope scope = new SharedWithScope("scope1", new SharedWithScope.ScopeRecipients(Map.of())); Set scopes = new HashSet<>(); scopes.add(scope); @@ -136,7 +167,7 @@ public void testToXContentBuildsCorrectly() throws IOException { String result = builder.toString(); - String expected = "{\"scope1\":{\"users\":[],\"roles\":[],\"backend_roles\":[]}}"; + String expected = "{\"scope1\":{}}"; MatcherAssert.assertThat(expected.length(), equalTo(result.length())); MatcherAssert.assertThat(expected, equalTo(result)); @@ -154,7 +185,7 @@ public void testWriteToWithEmptySet() throws IOException { public void testWriteToWithIOException() throws IOException { Set set = new HashSet<>(); - set.add(new SharedWithScope("test", new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of()))); + set.add(new SharedWithScope("test", new SharedWithScope.ScopeRecipients(Map.of()))); ShareWith shareWith = new ShareWith(set); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -166,7 +197,7 @@ public void testWriteToWithIOException() throws IOException { 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(Set.of(), Set.of(), Set.of()))); + largeSet.add(new SharedWithScope("scope" + i, new SharedWithScope.ScopeRecipients(Map.of()))); } ShareWith shareWith = new ShareWith(largeSet); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -192,12 +223,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(Set.of(), Set.of(), Set.of())) - ); - sharedWithScopes.add( - new SharedWithScope(ResourceAccessScope.READ_WRITE, new SharedWithScope.ScopeRecipients(Set.of(), Set.of(), Set.of())) - ); + 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); @@ -206,4 +233,25 @@ public void test_writeSharedWithScopesToStream() throws IOException { 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; + } } From 7a868cb6d1f9718599a889983dafc04c4b1408c2 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 16:47:49 -0500 Subject: [PATCH 47/51] Fixes gradle check failures Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/RecipientTypeRegistry.java | 4 ++-- .../opensearch/accesscontrol/resources/SharedWithScope.java | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java b/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java index 9846933c236e7..7436093e1b257 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java @@ -20,11 +20,11 @@ public class RecipientTypeRegistry { private static final Map REGISTRY = new HashMap<>(); public static void registerRecipientType(String key, RecipientType recipientType) { - REGISTRY.put(key.toUpperCase(), recipientType); + REGISTRY.put(key, recipientType); } public static RecipientType fromValue(String value) { - RecipientType type = REGISTRY.get(value.toUpperCase()); + RecipientType type = REGISTRY.get(value); if (type == null) { throw new IllegalArgumentException("Unknown RecipientType: " + value); } diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index f1b2fff5dfba9..7f9cf6a3a1004 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -103,6 +103,9 @@ public static class ScopeRecipients implements ToXContentFragment, NamedWriteabl private final Map> recipients; public ScopeRecipients(Map> recipients) { + if (recipients == null) { + throw new IllegalArgumentException("Recipients map cannot be null"); + } this.recipients = recipients; } @@ -129,7 +132,7 @@ public static ScopeRecipients fromXContent(XContentParser parser) throws IOExcep while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { String fieldName = parser.currentName(); - RecipientType recipientType = RecipientTypeRegistry.fromValue(fieldName.toUpperCase()); + RecipientType recipientType = RecipientTypeRegistry.fromValue(fieldName); parser.nextToken(); Set values = new HashSet<>(); From f1d30117917c701aca698e3c81c25c3748a4a5b5 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 20:26:11 -0500 Subject: [PATCH 48/51] Fixes created by tests Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/CreatedBy.java | 66 +++- .../resources/CreatedByTests.java | 285 ++++++++++++++++++ 2 files changed, 348 insertions(+), 3 deletions(-) create mode 100644 server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java index 19b71fea5271f..889952b3b8a69 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/CreatedBy.java @@ -9,7 +9,10 @@ package org.opensearch.accesscontrol.resources; 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; import java.io.IOException; @@ -20,10 +23,67 @@ * * @opensearch.experimental */ -public abstract class CreatedBy implements ToXContentFragment, NamedWriteable { +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(); + } - // Non-default implementation provided by security plugin public static CreatedBy fromXContent(XContentParser parser) throws IOException { - return null; + 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/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java new file mode 100644 index 0000000000000..bd86cb36fcfa2 --- /dev/null +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/CreatedByTests.java @@ -0,0 +1,285 @@ +/* + * 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.accesscontrol.resources; + +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 org.hamcrest.MatcherAssert; + +import java.io.IOException; + +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)); + } + +} From 00ba7a7368e588f8d8268b40d3d5f95f3dd0b647 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 21:09:40 -0500 Subject: [PATCH 49/51] Adds test for recipient registry Signed-off-by: Darshit Chanpura --- .../resources/RecipientTypeRegistry.java | 2 +- .../resources/RecipientTypeRegistryTests.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 server/src/test/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistryTests.java diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java b/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java index 7436093e1b257..e0c56d6f7c2c1 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistry.java @@ -26,7 +26,7 @@ public static void registerRecipientType(String key, RecipientType recipientType public static RecipientType fromValue(String value) { RecipientType type = REGISTRY.get(value); if (type == null) { - throw new IllegalArgumentException("Unknown RecipientType: " + value); + throw new IllegalArgumentException("Unknown RecipientType: " + value + ". Must be 1 of these: " + REGISTRY.values()); } return type; } diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistryTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistryTests.java new file mode 100644 index 0000000000000..8ed5ebaba2e2d --- /dev/null +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/RecipientTypeRegistryTests.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.accesscontrol.resources; + +import org.opensearch.test.OpenSearchTestCase; +import org.hamcrest.MatcherAssert; + +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()))); + } +} From 6fb505548924089e2223c0d9b1500086398130e1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 30 Dec 2024 22:12:34 -0500 Subject: [PATCH 50/51] Fixes a bug in toXContent Signed-off-by: Darshit Chanpura --- .../accesscontrol/resources/SharedWithScope.java | 2 -- .../accesscontrol/resources/ShareWithTests.java | 10 +++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java index 7f9cf6a3a1004..01f0982f1ca76 100644 --- a/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java +++ b/server/src/main/java/org/opensearch/accesscontrol/resources/SharedWithScope.java @@ -160,11 +160,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (recipients.isEmpty()) { return builder; } - builder.startObject(); for (Map.Entry> entry : recipients.entrySet()) { builder.array(entry.getKey().getType(), entry.getValue().toArray()); } - builder.endObject(); return builder; } } diff --git a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java index deb4fa8fc15b0..e1af5a0900e2d 100644 --- a/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java +++ b/server/src/test/java/org/opensearch/accesscontrol/resources/ShareWithTests.java @@ -12,6 +12,7 @@ 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.test.OpenSearchTestCase; @@ -154,7 +155,10 @@ public void testFromXContentWithUnexpectedEndOfInput() throws IOException { } public void testToXContentBuildsCorrectly() throws IOException { - SharedWithScope scope = new SharedWithScope("scope1", new SharedWithScope.ScopeRecipients(Map.of())); + SharedWithScope scope = new SharedWithScope( + "scope1", + new SharedWithScope.ScopeRecipients(Map.of(new RecipientType("users"), Set.of("bleh"))) + ); Set scopes = new HashSet<>(); scopes.add(scope); @@ -163,11 +167,11 @@ public void testToXContentBuildsCorrectly() throws IOException { XContentBuilder builder = JsonXContent.contentBuilder(); - shareWith.toXContent(builder, null); + shareWith.toXContent(builder, ToXContent.EMPTY_PARAMS); String result = builder.toString(); - String expected = "{\"scope1\":{}}"; + String expected = "{\"scope1\":{\"users\":[\"bleh\"]}}"; MatcherAssert.assertThat(expected.length(), equalTo(result.length())); MatcherAssert.assertThat(expected, equalTo(result)); From 532d13ac016d24827003694938899d3ca56624af Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Tue, 31 Dec 2024 15:45:56 -0500 Subject: [PATCH 51/51] Moves jackson dependency addition to server build.gradle Signed-off-by: Darshit Chanpura --- libs/core/build.gradle | 2 -- libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 | 1 - libs/core/licenses/jackson-databind-2.18.2.jar.sha1 | 1 - libs/x-content/build.gradle | 6 ++++++ server/build.gradle | 3 +++ 5 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 delete mode 100644 libs/core/licenses/jackson-databind-2.18.2.jar.sha1 diff --git a/libs/core/build.gradle b/libs/core/build.gradle index f381aba7c7549..0cf2cd0bf92b6 100644 --- a/libs/core/build.gradle +++ b/libs/core/build.gradle @@ -40,8 +40,6 @@ dependencies { api project(':libs:opensearch-common') api "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" - api "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" - api "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" // lucene api "org.apache.lucene:lucene-core:${versions.lucene}" diff --git a/libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 b/libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 deleted file mode 100644 index a06e1d5f28425..0000000000000 --- a/libs/core/licenses/jackson-annotations-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -985d77751ebc7fce5db115a986bc9aa82f973f4a \ No newline at end of file diff --git a/libs/core/licenses/jackson-databind-2.18.2.jar.sha1 b/libs/core/licenses/jackson-databind-2.18.2.jar.sha1 deleted file mode 100644 index eedbfff66c705..0000000000000 --- a/libs/core/licenses/jackson-databind-2.18.2.jar.sha1 +++ /dev/null @@ -1 +0,0 @@ -deef8697b92141fb6caf7aa86966cff4eec9b04f \ No newline at end of file diff --git a/libs/x-content/build.gradle b/libs/x-content/build.gradle index 3be13dc2926fd..dab07c5af336a 100644 --- a/libs/x-content/build.gradle +++ b/libs/x-content/build.gradle @@ -58,6 +58,12 @@ tasks.named('forbiddenApisMain').configure { replaceSignatureFiles 'jdk-signatures' } +thirdPartyAudit.ignoreMissingClasses( + // from com.fasterxml.jackson.dataformat.yaml.YAMLMapper (jackson-dataformat-yaml) + 'com.fasterxml.jackson.databind.ObjectMapper', + 'com.fasterxml.jackson.databind.cfg.MapperBuilder' +) + tasks.named("dependencyLicenses").configure { mapping from: /jackson-.*/, to: 'jackson' } diff --git a/server/build.gradle b/server/build.gradle index 8dd23491ccd69..57ff6f2619817 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -70,6 +70,9 @@ dependencies { api project(":libs:opensearch-telemetry") api project(":libs:opensearch-task-commons") + api "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + api "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}" + compileOnly project(':libs:opensearch-plugin-classloader') testRuntimeOnly project(':libs:opensearch-plugin-classloader')