Map extension routes to permission-able action names#622
Map extension routes to permission-able action names#622cwperks wants to merge 69 commits intoopensearch-project:mainfrom
Conversation
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
|
Did a quick pass. The changes overall LGTM @cwperks! I'm still trying to figure out the best place for these changes. Should it be in OpenSearch like we have ExtensionRestRequest there or SDK? |
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
| public static ExtensionRouteHandlerFactory getInstance() { | ||
| if(INSTANCE == null) { | ||
| INSTANCE = new ExtensionRouteHandlerFactory(); | ||
| } |
There was a problem hiding this comment.
Are we trying to create SINGLETON object for INSTANCE here? if yes is it multithreaded safe?
There was a problem hiding this comment.
Yes, the problem I was facing is that I needed the extensions shortName (abbreviation) inside the RestHandler classes like RestHelloAction in the sample extension. Instead of passing the value as a constructor arg or passing that extension runner into those classes, I thought it would be better to have singular place where its registered and can be used throughout.
Do you know how I can test if this is multi-threaded safe?
| import org.opensearch.OpenSearchException; | ||
|
|
||
| public class ExtensionRouteHandlerFactory { | ||
| private static ExtensionRouteHandlerFactory INSTANCE; |
There was a problem hiding this comment.
Do you think we can use Guice here to achieve this?
There was a problem hiding this comment.
That would be great. I'll look into it. The main problem I was facing was that I needed to have the shortName of the extension available in the REST Handler (i.e. inside of RestHelloAction for the sample extension). The way I went about doing that was to create a singleton class that could be fetched throughout the SDK but if there is a better way to do it then I will switch to using that.
There was a problem hiding this comment.
if there is a better way to do it then I will switch to using that.
Extensions are always initialized with their settings. getExtensionSettings() must be implemented on the Extension interface. (The BaseExtension class offers the convenience of either a settings object or yml file.)
Any method on the mainExtension class ought to be able to call getExtensionSettings().getShortExtensionName() to access this value without any factory/singletons.
In the AD Extension we usually pass the ExtensionsRunner class to every REST handler, and we have a getter on the runner for the extension. So all the AD REST handlers can do runner.getExtension().getExtensionSettings().getShortExtensionName().
| public ExtensionSettings(String extensionName, String hostAddress, String hostPort, String opensearchAddress, String opensearchPort) { | ||
| public ExtensionSettings( | ||
| String extensionName, | ||
| String shortName, |
There was a problem hiding this comment.
| String shortName, | |
| String shortExtensionName, |
There was a problem hiding this comment.
I'll update it, I was torn between different names for this variable. One of the naming ideas was extensionAbbr, but I didn't know if was appropriate to use an abbreviation in a variable name signifying that this variable is an abbreviation. It felt like it was a crossword puzzle clue.
| (mHandlers, newMHandler) -> mHandlers.addMethods(extensionRestHandler, method) | ||
| ); | ||
| String restPathWithName = restPathToString(method, path, name); | ||
| registeredPaths.add(restPathWithName); |
There was a problem hiding this comment.
We are missing adding paths to deprecated paths here. One way to handle this would be to merge both the registerHandler methods and add a check for name.
|
|
||
| public String getShortName() { | ||
| return shortName; | ||
| } |
There was a problem hiding this comment.
This getter should have a test in TestExtensionSettings.java
There was a problem hiding this comment.
This has been added to a few different tests to verify its behavior
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
Codecov Report
@@ Coverage Diff @@
## main #622 +/- ##
============================================
+ Coverage 43.57% 43.61% +0.03%
- Complexity 314 324 +10
============================================
Files 69 71 +2
Lines 1983 2011 +28
Branches 141 144 +3
============================================
+ Hits 864 877 +13
- Misses 1101 1113 +12
- Partials 18 21 +3
|
Signed-off-by: Craig Perkins <[email protected]>
|
Can the maintainers take another look at this PR? |
dbwiddis
left a comment
There was a problem hiding this comment.
I think this is overly complex. Details below.
| * compatible open source license. | ||
| */ | ||
|
|
||
| package org.opensearch.sdk; |
There was a problem hiding this comment.
If this class stays, it should not be in the base package. Probably would fit in the .rest subpackage.
| String path, | ||
| Function<RestRequest, ExtensionRestResponse> handler | ||
| ) { | ||
| super(ExtensionRouteHandlerFactory.getInstance().generateRouteName(handlerName), method, path, handler); |
There was a problem hiding this comment.
I'm trying to understand why we need an entire (sub)class just to call a generator function for one parameter. I don't think this class is necessary; we should just stick in an appropriate (probably static) method that does this name generation whenever we call new RouteHandler() from an extension.
| * @return Returns a name prepended with the extension's shortName | ||
| */ | ||
| public String generateRouteName(String handlerName) { | ||
| return extensionShortName + ":" + handlerName; |
There was a problem hiding this comment.
This method call doesn't check whether extensionShortName has been initialized. ExtensionRouteHandlerFactory.getInstance().generateRouteName() will throw an exception.
There's no documentation in this method that it requires initialization first.
This seems like a whole lot of class overhead, initializations, and initialization checks to create a factory object whose only purpose seems to be to prepend one string to another, that seems to be possible with a simple static method somewhere.
| public class ExtensionSettings { | ||
|
|
||
| private String extensionName; | ||
| private String shortExtensionName; |
There was a problem hiding this comment.
Given that this is used for singleton security setup later, should it be final?
| private String hostPort; | ||
| private String opensearchAddress; | ||
| private String opensearchPort; | ||
| private Map<String, String> securitySettings; |
| logger.info("SSL is " + sslText + " for transport"); | ||
| this.settings = settingsBuilder.build(); | ||
|
|
||
| if (extensionSettings.getShortExtensionName() != null) { |
There was a problem hiding this comment.
When is it possible for this to be null? It looks like you've required it in all the ExtensionSettings constructors.
Assuming you allow someone to pass null to the constructor, then not including it means the factory is never initialized (assuming we need a factory) which means that all attempts to generate a route name without checking for the initialization will throw an exception.
| if (extensionSettings.getShortExtensionName() != null) { | ||
| // initialize ExtensionRouteHandlerFactory | ||
| ExtensionRouteHandlerFactory factory = ExtensionRouteHandlerFactory.getInstance(); | ||
| if (!factory.isInitialized()) { |
There was a problem hiding this comment.
I'm trying to think of a situation where this would ever be initialized before the ExtensionsRunner constructor was called. Possible but someone would have to go out of their way to do it.
Not sure we need a factory at all, and this is just one more reason this solution is overly complex.
| if (name.isPresent()) { | ||
| registeredPaths.add(restPathToString(method, path, name)); | ||
| } else { | ||
| registeredPaths.add(restPathToString(method, path, name)); |
There was a problem hiding this comment.
The method called in else is exactly the same as the method called in the isPresent() case. Why does this conditional exist?
There was a problem hiding this comment.
Removed this conditional
| * @return A string appending the method and path. | ||
| */ | ||
| public static String restPathToString(Method method, String path) { | ||
| public static String restPathToString(Method method, String path, Optional<String> name) { |
There was a problem hiding this comment.
Originally this was a more important method but I think it's been reduced to a cosmetic/logging/exception-message method as the rest paths are stored in the pathTrie. So I think the plain space-delimited text may not be useful or may be more confusing than using some other custom output here.
| new RouteHandler(POST, "/hello", handlePostRequest), | ||
| new RouteHandler(PUT, "/hello/{name}", handlePutRequest), | ||
| new RouteHandler(DELETE, "/goodbye", handleDeleteRequest) | ||
| new ExtensionRouteHandler("greet", GET, "/hello", handleGetRequest), |
There was a problem hiding this comment.
This appears to be the only case where we are actually using the new ExtensionRouteHandler class.
This method signature is an override that only exists if we are extending BaseExtensionRestHandler so it seems to me we could add all this logic in that superclass. The BaseExtensionRestHandler class could fetch the shortname from the settings and then just expose a protected routePrefix(String name) method that returns "shortname:name". Then this line would just be new RouteHandler(routePrefix("greet"), GET, ...) and you'd save a ton of intermediate objects and initialization checking, etc.
Signed-off-by: Craig Perkins <[email protected]>
Signed-off-by: Craig Perkins <[email protected]>
|
@dbwiddis I removed the ExtensionRouteHandler and ExtensionRouteHandlerFactory in favor of your suggestion of including the |
|
Closing in favor of #827 |
Description
This PR relies on an an earlier PR (#619) to setup TLS for extensions.
This PR also has a companion core and security PR
Authorize Requests bound for extensions in the REST-Layer security#2601Isolated changes can be viewed using the compare tool: cwperks/opensearch-sdk-java@setup-extension-tls...ssl-and-handler-naming
This change modifies the extension settings by adding a new
shortNamesetting (an abbreviation for an extension) that is used when creating a name for a route./helloof the HelloWorld extension is mapped to the permission-able nameextensions:hw/greet. The RouteHandler moves to core as part of this change, its needed by the security plugin to determine if a RestRequest is destined for an extension.Endpoint registration has been updated to send the name of the route to core upon registration.
Issues Resolved
Related to opensearch-project/security#2589
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.