-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Map extension routes to permission-able action names #622
Conversation
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
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 <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
public static ExtensionRouteHandlerFactory getInstance() { | ||
if(INSTANCE == null) { | ||
INSTANCE = new ExtensionRouteHandlerFactory(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we trying to create SINGLETON object for INSTANCE here? if yes is it multithreaded safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we can use Guice here to achieve this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String shortName, | |
String shortExtensionName, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
(mHandlers, newMHandler) -> mHandlers.addMethods(extensionRestHandler, method) | ||
); | ||
String restPathWithName = restPathToString(method, path, name); | ||
registeredPaths.add(restPathWithName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
.
this.securitySettings = securitySettings; | ||
} | ||
|
||
public String getExtensionName() { | ||
return extensionName; | ||
} | ||
|
||
public String getShortName() { | ||
return shortName; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This getter should have a test in TestExtensionSettings.java
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been added to a few different tests to verify its behavior
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
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 <cwperx@amazon.com>
Can the maintainers take another look at this PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is overly complex. Details below.
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.sdk; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -49,10 +49,12 @@ | |||
public class ExtensionSettings { | |||
|
|||
private String extensionName; | |||
private String shortExtensionName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is used for singleton security setup later, should it be final
?
private String hostAddress; | ||
private String hostPort; | ||
private String opensearchAddress; | ||
private String opensearchPort; | ||
private Map<String, String> securitySettings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also be final
?
@@ -194,6 +197,14 @@ protected ExtensionsRunner(Extension extension) throws IOException { | |||
logger.info("SSL is " + sslText + " for transport"); | |||
this.settings = settingsBuilder.build(); | |||
|
|||
if (extensionSettings.getShortExtensionName() != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@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
shortName
setting (an abbreviation for an extension) that is used when creating a name for a route./hello
of 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.