diff --git a/server/src/main/java/org/opensearch/cluster/ApplicationManager.java b/server/src/main/java/org/opensearch/cluster/ApplicationManager.java deleted file mode 100644 index 37c472c529f9a..0000000000000 --- a/server/src/main/java/org/opensearch/cluster/ApplicationManager.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.cluster; - -import java.util.concurrent.atomic.AtomicReference; -import org.opensearch.extensions.ExtensionsManager; - -/** - * The ApplicationManager class handles the processing and resolution of multiple types of applications. Using the class, OpenSearch can - * continue to resolve requests even when specific application types are disabled. For example, the ExtensionManager can be Noop in which case - * the ApplicationManager is able to resolve requests for other application types still - * - * @opensearch.experimental - */ -public class ApplicationManager { - - AtomicReference extensionManager; - public static ApplicationManager instance; // Required for access in static contexts - - public ApplicationManager() { - instance = this; - extensionManager = new AtomicReference<>(); - } - - public void register(ExtensionsManager manager) { - if (this.extensionManager == null) { - this.extensionManager = new AtomicReference<>(manager); - } else { - this.extensionManager.set(manager); - } - } - -} diff --git a/server/src/main/java/org/opensearch/extensions/DiscoveryExtensionNode.java b/server/src/main/java/org/opensearch/extensions/DiscoveryExtensionNode.java index dde7b9c079f16..801c9bfb195a4 100644 --- a/server/src/main/java/org/opensearch/extensions/DiscoveryExtensionNode.java +++ b/server/src/main/java/org/opensearch/extensions/DiscoveryExtensionNode.java @@ -9,13 +9,11 @@ package org.opensearch.extensions; import java.io.IOException; -import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import org.opensearch.Application; import org.opensearch.OpenSearchException; @@ -28,21 +26,19 @@ import org.opensearch.core.common.io.stream.Writeable; import org.opensearch.core.xcontent.ToXContentFragment; import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.identity.Subject; import org.opensearch.identity.scopes.Scope; -import org.opensearch.identity.tokens.AuthToken; /** * Discover extensions running independently or in a separate process * * @opensearch.internal */ -public class DiscoveryExtensionNode extends DiscoveryNode implements Writeable, ToXContentFragment, Subject, Application { +public class DiscoveryExtensionNode extends DiscoveryNode implements Writeable, ToXContentFragment, Application { private Version minimumCompatibleVersion; private List dependencies = Collections.emptyList(); private List implementedInterfaces = Collections.emptyList(); - private List scopes = List.of(); + private final List scopes; public DiscoveryExtensionNode( String name, @@ -145,14 +141,4 @@ private void validate() { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { return null; } - - @Override - public void authenticate(AuthToken token) { - - } - - @Override - public Optional getApplication() { - return Optional.of(this.getPrincipal()); - } } diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java index 935ccd0074f08..f815c32dd3fc3 100644 --- a/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java +++ b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.net.InetAddress; -import java.security.Principal; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -50,7 +49,6 @@ import org.opensearch.extensions.rest.RestActionsRequestHandler; import org.opensearch.extensions.settings.CustomSettingsRequestHandler; import org.opensearch.extensions.settings.RegisterCustomSettingsRequest; -import org.opensearch.identity.scopes.Scope; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.ConnectTransportException; import org.opensearch.transport.TransportException; @@ -511,19 +509,4 @@ Settings getEnvironmentSettings() { public Set> getAdditionalSettings() { return this.additionalSettings; } - - public Set getScopes(Principal principal) { - if (this.getExtensionIdMap().containsKey(principal.getName())) { - return this.getExtensionIdMap().get(principal.getName()).getScopes(); - } - return Set.of(); - } - - /** Checks whether there is an application associated with the given principal or not - * @param principal The principal for the application you are trying to find - * @return Whether the application exists (TRUE) or not (FALSE) - * */ - public boolean applicationExists(Principal principal) { - return (this.getExtensionIdMap().containsKey(principal.getName())); - } } diff --git a/server/src/main/java/org/opensearch/identity/ApplicationAwareSubject.java b/server/src/main/java/org/opensearch/identity/ApplicationAwareSubject.java index e39bac8cb742f..9bdca99396238 100644 --- a/server/src/main/java/org/opensearch/identity/ApplicationAwareSubject.java +++ b/server/src/main/java/org/opensearch/identity/ApplicationAwareSubject.java @@ -16,7 +16,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import org.opensearch.extensions.ExtensionsManager; + import org.opensearch.identity.scopes.ApplicationScope; import org.opensearch.identity.scopes.Scope; import org.opensearch.identity.tokens.AuthToken; @@ -33,24 +33,15 @@ public class ApplicationAwareSubject implements Subject { private final Subject wrapped; - private final ExtensionsManager extensionsManager; + private final ApplicationManager applicationManager; /** * We wrap a basic Subject object to create an ApplicationAwareSubject -- this should come from the IdentityService * @param wrapped The Subject to be wrapped */ - public ApplicationAwareSubject(final Subject wrapped, ExtensionsManager extensionsManager) { + public ApplicationAwareSubject(final Subject wrapped, final ApplicationManager applicationManager) { this.wrapped = wrapped; - this.extensionsManager = extensionsManager; - } - - /** - * Use the ApplicationManager to get the scopes associated with the principal of the wrapped Subject. - * Because the wrapped subject is just a basic Subject, it may not know its own scopes. This circumvents this issue. - * @return A set of Strings representing the scopes associated with the wrapped subject's principal - */ - public Set getScopes() { - return extensionsManager.getScopes(wrapped.getPrincipal()); + this.applicationManager = applicationManager; } /** @@ -60,36 +51,29 @@ public Set getScopes() { */ public boolean isAllowed(final List scopes) { - final Optional optionalPrincipal = this.getApplication(); - - if (optionalPrincipal.isEmpty()) { + final Optional application = this.getApplication(); + if (application.isEmpty()) { // If there is no application, actions are allowed by default - return true; } - if (!extensionsManager.applicationExists(this.getPrincipal())) { - + final Optional> scopesOfApplication = applicationManager.getScopes(application.get()); + if (scopesOfApplication.isEmpty()) { + // If no matching application was found, actions are denied by default return false; } - final Set scopesOfApplication = this.getScopes(); - - boolean isApplicationSuperUser = scopesOfApplication.contains(ApplicationScope.SUPER_USER_ACCESS); - + final boolean isApplicationSuperUser = scopesOfApplication.get().contains(ApplicationScope.SUPER_USER_ACCESS); if (isApplicationSuperUser) { - return true; } - Set intersection = new HashSet<>(scopesOfApplication); - // Retain only the elements present in the list - intersection.retainAll(scopes); - - boolean isMatchingScopePresent = !intersection.isEmpty(); + final Set scopesCopy = new HashSet<>(scopesOfApplication.get()); + scopesCopy.retainAll(scopes); - return isMatchingScopePresent; + final boolean hasMatchingScopes = !scopesCopy.isEmpty(); + return hasMatchingScopes; } // Passthroughs for wrapped subject @@ -114,7 +98,7 @@ public boolean equals(Object obj) { if (!(obj instanceof ApplicationAwareSubject)) { return false; } - ApplicationAwareSubject other = (ApplicationAwareSubject) obj; - return Objects.equals(this.getScopes(), other.getScopes()); + final ApplicationAwareSubject other = (ApplicationAwareSubject) obj; + return Objects.equals(this.wrapped, other.wrapped); } } diff --git a/server/src/main/java/org/opensearch/identity/ApplicationManager.java b/server/src/main/java/org/opensearch/identity/ApplicationManager.java new file mode 100644 index 0000000000000..5701ec1a37d93 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/ApplicationManager.java @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +import java.security.Principal; +import java.util.concurrent.atomic.AtomicReference; +import java.util.Optional; +import java.util.Set; +import org.opensearch.extensions.ExtensionsManager; +import org.opensearch.identity.scopes.Scope; + +/** + * The ApplicationManager class handles the processing and resolution of multiple types of applications. Using the class, OpenSearch can + * continue to resolve requests even when specific application types are disabled. For example, the ExtensionManager can be Noop in which case + * the ApplicationManager is able to resolve requests for other application types still + * + * @opensearch.experimental + */ +public class ApplicationManager { + + private final AtomicReference extensionManager = new AtomicReference(); + + public void register(final ExtensionsManager manager) { + // Only allow the extension manager to be registered the first time + extensionManager.compareAndSet(null, manager); + } + + public Optional> getScopes(final Principal applicationPrincipal) { + return findExtension(applicationPrincipal); + } + + private Optional> findExtension(final Principal applicationPrincipal) { + if (extensionManager.get().getExtensionIdMap().containsKey(applicationPrincipal.getName())) { + return Optional.of(extensionManager.get().getExtensionIdMap().get(applicationPrincipal.getName()).getScopes()); + } + return Optional.empty(); + } +} diff --git a/server/src/main/java/org/opensearch/identity/IdentityService.java b/server/src/main/java/org/opensearch/identity/IdentityService.java index 969b4f953777d..47162a6e678fe 100644 --- a/server/src/main/java/org/opensearch/identity/IdentityService.java +++ b/server/src/main/java/org/opensearch/identity/IdentityService.java @@ -11,7 +11,6 @@ import org.apache.logging.log4j.Logger; import org.opensearch.OpenSearchException; import org.opensearch.common.settings.Settings; -import org.opensearch.extensions.ExtensionsManager; import org.opensearch.identity.noop.NoopIdentityPlugin; import org.opensearch.identity.tokens.TokenManager; import org.opensearch.plugins.IdentityPlugin; @@ -26,11 +25,15 @@ public class IdentityService { private final Settings settings; private final IdentityPlugin identityPlugin; - private final ExtensionsManager extensionsManager; + private final ApplicationManager applicationManager; - public IdentityService(ExtensionsManager extensionsManager, final Settings settings, final List identityPlugins) { + public IdentityService( + final Settings settings, + final List identityPlugins, + final ApplicationManager applicationManager + ) { this.settings = settings; - this.extensionsManager = extensionsManager; + this.applicationManager = applicationManager; if (identityPlugins.size() == 0) { log.debug("Identity plugins size is 0"); @@ -50,7 +53,7 @@ public IdentityService(ExtensionsManager extensionsManager, final Settings setti * Gets the current Subject */ public ApplicationAwareSubject getSubject() { - return new ApplicationAwareSubject(identityPlugin.getSubject(), extensionsManager); + return new ApplicationAwareSubject(identityPlugin.getSubject(), applicationManager); } /** diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index 661aa43a3d46e..0f0664148583f 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -83,6 +83,7 @@ import org.opensearch.bootstrap.BootstrapContext; import org.opensearch.client.Client; import org.opensearch.client.node.NodeClient; +import org.opensearch.identity.ApplicationManager; import org.opensearch.cluster.ClusterInfoService; import org.opensearch.cluster.ClusterModule; import org.opensearch.cluster.ClusterName; @@ -463,6 +464,7 @@ protected Node( ); final Settings settings = pluginsService.updatedSettings(); + final ApplicationManager applicationManager = new ApplicationManager(); // Ensure to initialize Feature Flags via the settings from opensearch.yml FeatureFlags.initializeFeatureFlags(settings); @@ -476,6 +478,7 @@ protected Node( ); identityPlugins.addAll(pluginsService.filterPlugins(IdentityPlugin.class)); } + this.identityService = new IdentityService(settings, identityPlugins, applicationManager); if (FeatureFlags.isEnabled(FeatureFlags.EXTENSIONS)) { final List extensionAwarePlugins = pluginsService.filterPlugins(ExtensionAwarePlugin.class); @@ -487,8 +490,7 @@ protected Node( } else { this.extensionsManager = new NoopExtensionsManager(); } - - this.identityService = new IdentityService(extensionsManager, settings, identityPlugins); + applicationManager.register(extensionsManager); final Set additionalRoles = pluginsService.filterPlugins(Plugin.class) .stream() diff --git a/server/src/main/java/org/opensearch/plugins/wrappers/ScopeProtectedActionPlugin.java b/server/src/main/java/org/opensearch/plugins/wrappers/ScopeProtectedActionPlugin.java index 9edc15e4440af..7ea835a71d51b 100644 --- a/server/src/main/java/org/opensearch/plugins/wrappers/ScopeProtectedActionPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/wrappers/ScopeProtectedActionPlugin.java @@ -32,10 +32,8 @@ package org.opensearch.plugins.wrappers; -import java.security.Principal; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.function.Supplier; import java.util.function.UnaryOperator; import org.opensearch.OpenSearchException; @@ -54,9 +52,7 @@ import org.opensearch.common.settings.SettingsFilter; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.identity.IdentityService; -import org.opensearch.identity.Subject; import org.opensearch.identity.scopes.ExtensionPointScope; -import org.opensearch.identity.tokens.AuthToken; import org.opensearch.plugins.ActionPlugin; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; @@ -67,7 +63,7 @@ * * @opensearch.experimental */ -public class ScopeProtectedActionPlugin implements ActionPlugin, Subject { +public class ScopeProtectedActionPlugin implements ActionPlugin { private final ActionPlugin plugin; private final IdentityService identity; @@ -151,18 +147,4 @@ public Collection> ind checkIfAllowed(); return plugin.indicesAliasesRequestValidators(); } - - // Implement to have access to identity methods - @Override - public Principal getPrincipal() { - return identity.getSubject().getPrincipal(); - } - - @Override - public void authenticate(AuthToken token) {} - - @Override - public Optional getApplication() { - return Optional.of(identity.getSubject().getPrincipal()); - } } diff --git a/test/framework/src/main/java/org/opensearch/test/rest/RestActionTestCase.java b/test/framework/src/main/java/org/opensearch/test/rest/RestActionTestCase.java index da0fbf928535f..2d0585e5a1435 100644 --- a/test/framework/src/main/java/org/opensearch/test/rest/RestActionTestCase.java +++ b/test/framework/src/main/java/org/opensearch/test/rest/RestActionTestCase.java @@ -35,7 +35,6 @@ import java.io.IOException; import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import org.junit.After; @@ -47,7 +46,6 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.extensions.ExtensionsManager; import org.opensearch.identity.IdentityService; import org.opensearch.indices.breaker.NoneCircuitBreakerService; import org.opensearch.rest.RestController; @@ -57,6 +55,7 @@ import org.opensearch.test.OpenSearchTestCase; import org.opensearch.test.client.NoOpNodeClient; import org.opensearch.usage.UsageService; +import org.opensearch.identity.ApplicationManager; /** * A common base class for Rest*ActionTests. Provides access to a {@link RestController} @@ -69,7 +68,7 @@ public abstract class RestActionTestCase extends OpenSearchTestCase { @Before public void setUpController() throws IOException { verifyingClient = new VerifyingClient(this.getTestName()); - final IdentityService identityService = new IdentityService(new ExtensionsManager(Set.of()), Settings.EMPTY, List.of()); + final IdentityService identityService = new IdentityService(Settings.EMPTY, List.of(), new ApplicationManager()); controller = new RestController( Collections.emptySet(), null,