-
Notifications
You must be signed in to change notification settings - Fork 285
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
Auth Module for Connector Services #571
Conversation
final QualifiedName name, | ||
final String filter, | ||
final Integer limit) { | ||
authorize(MetacatContextManager.getContext(), 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.
Now that you're adding a new AuthEnabledConnector
services in addition to existing ThrottlingConnector
ones there is an opportunity for some refactor and improvement. Instead of overriding each method similar to what the throttling connector already does, there could be a parent class that does these overrides and calls an abstract check
and both auth and throttling provide their own implementation of 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 being the case, this update could also be done in a later change.
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.
addressed all comments in a refactor to combine auth and throttling into one validating class 😄
delegate.rename(context, oldName, newName); | ||
} | ||
|
||
private void authorize(final MetacatRequestContext context, final QualifiedName resource) { |
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.
Adding to the previous comment, using such a framework and extending it would make it so you wouldn't need to have basically the same method and logic duplicated three times across classes which is not a great pattern for code structure or maintainability.
*/ | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class AuthEnabledConnectorDatabaseService implements ConnectorDatabaseService { |
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.
There is a ConnectorBaseService
that the different types extend so having certain generalizable functionality that is needed across different types there is also an option if you see such an opportunity though up to you.
|
||
@Override | ||
public void create(final ConnectorRequestContext context, final CatalogInfo resource) { | ||
checkThrottling(MetacatContextManager.getContext().getRequestName(), resource.getName()); | ||
if (rateLimiterEnabled) { |
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.
nit: I feel like this part of the code (two if statements) can be refactored across Catalog/Database/Partition/Table Service.
Option 1: Just have a simple Util function that covers these 2 if statements, which can be reused across different services/functions.
Option 2: Have a Validator Interface, and for now, we can have two classes (say one for Throttling and another one for Authorization) implements this interface. And each Service can contain a list of validator to run before triggering the delegate call. And in the future, if we want to add more validation logic, we can just add a new Validator.
wdyt?
If we have tight deadline, we can also do refactoring after.
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.
yeah i like this idea; the only consideration is that the decision to throttle is actually at the service-method granularity atm. because checkThrottling
requires a resource (QualifiedName
), throttling is not checked (even if enabled) for certain methods (example). this was existing logic before this PR.
@insyncoss and i discussed removing this special casing and making the resource nullable so that checkThrottling
can be called indiscriminately across methods in a service. it would require null checking in checkThrottling and this would be necessary whether we go with Option 1 or Option 2.
Option 2 is probably better; there is also some validation in RequestGateway elsewhere in the repo that could be pulled into this later on if we go with Option 2.
for Option 2, would you suggest having configs for each connector-service within the connector config, e.g. connector.rate-limiter-exempted-catalog-service
or do you think there's a better place for them?
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.
For option2, maybe I miss something, but I feel like we don't need to have a config for each service.
I was thinking we still keep the two original config extempted setting (one for throttling and one for authorization), and have just 1 Validator interface and 2 Implementations (one for throttling and another for Authorization).
The interface can have one function say validate, which takes in two parameters one for MetacatRequestContext, and another for QualifiedName
For the implementation for either ThrottlingValidator and AuthorizationValidator, each should one class variable say enabled, and during the initialization of the Validator, it should read the exempted value and then set the value to true or false accordingly. ThrottlingValidator should also creates the RateLimiter and AuthorizationValidator should creates the Authorization through bean initialization. And the implementation for validate function should be if its enabled, then trigger the corresponding validation.
Then for each service (table/partition/catalog/database), it should have a list of Validator. I see most of the functions check both Throttling and Authorization, so we can have a generic util static function that run all validators. And as you said for some functions where not all validators should run, we can handle them separately.
Looks like this will be a lot of code changes, and I am ok we keep the current version first. We can have others chime in to share their opinions as well.
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 see what you mean-- this is cleaner and more extensible. we will need to be careful to include the following:
- special handling for specific methods that do not check throttling even when enabled at the connector level
- ensure we are explicit about the order in which different validations occur. for example, always check throttling first, then auth
- (less important) retain the ability to switch the order of each validation at the method level. this is not needed now but could be as we add more to it
will wait for others opinions before making the change
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.
Here's a suggestion which iterates over the above suggestions:
- As Yingjian suggested, we should have a Validator interface with two implementations—ThrottlingValidator and AuthorizationValidator—that perform validations.
- Have a ValidationRegistry class that contains all the validators registered with it. It contains a method run that takes in a list of validations to be run. This will be used to execute the validations in the order or exact order they are to be run.
- Implement a ValidatingConnectorBase which implements a generic ConnectorBaseService. It takes the validation registry and a delegate and implements all the methods. Each method would first perform the validation via registry.
- Similarly, implement all the specific implementations for table, partition etc; with a similar logic.
} | ||
|
||
List<List> parametrizedCases() { | ||
return [ |
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.
nit: would better to have a comment on what these 4 columns represent
} | ||
|
||
then: | ||
if (rateLimiterEnabled || !rateLimiterEnabled) { |
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.
nit: the if statement might be redundant
} | ||
|
||
then: | ||
if (rateLimiterEnabled || !rateLimiterEnabled) { |
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.
nit: this if statement might be redundant
Looks like previously, there are two places where we don't checkThrottling ThrottlingConnectorPartitionService getPartitionNames Do we know why these two calls can be exempted from throttling check? |
@stevie9868 my undersanding these methods dont have a qualified resource associated with them, just prefix searches, and rate limiting is currently only on a per-resource basis |
@Builder | ||
@Getter | ||
@EqualsAndHashCode | ||
public class AuthorizationStatus { |
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 could be an enum and details could be decoupled from the status.
} | ||
|
||
return !Boolean.parseBoolean( | ||
connectorContext.getConfiguration().getOrDefault("connector.authorization-exempted", "false") |
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.
-
nit: static constant for "connector.authorization-exempted"
-
Additionally, there appears to be a discrepancy between the default values when the configuration is null and when the connector.
authorization-exempted
property is not set. Ideally, these defaults should be identical. -
authorization-exempted -> authorization-isEnabled ?
rateLimiter, | ||
rateLimiterEnabled, | ||
authorization, | ||
authorizationEnabled |
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 authorization object should have the capability to determine its own enabled status, rather than having it flagged externally.
rateLimiter, | ||
rateLimiterEnabled, | ||
authorization, | ||
authorizationEnabled |
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 authorization object should have the capability to determine its own enabled status, rather than having it flagged externally.
rateLimiter, | ||
rateLimiterEnabled, | ||
authorization, | ||
authorizationEnabled |
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 authorization object should have the capability to determine its own enabled status, rather than having it flagged externally.
if (rateLimiterEnabled) { | ||
checkThrottling(MetacatContextManager.getContext().getRequestName(), name); | ||
} | ||
if (authorizationEnabled) { |
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 responsibility for this and similar if
blocks should lie with the Authorization implementation, rather than the caller performing the checks.
* @param context The request context. | ||
* @return True if the request is authorized and False if unauthorized. | ||
*/ | ||
default AuthorizationStatus isAuthorized(@NonNull MetacatRequestContext context) { |
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 interface could be more explicit about its expectations from the MetacatRequestContext, instead of passing down the entire context.
This PR introduces the following:
MetacatRequestContext
and return an AuthorizationStatus. DefaultAuthorization can be overridden as needed with custom auth logicValidatingConnector{Catalog/Database/Partition/Table}Service
classes (e.g. ValidatingConnectorTableService) which subsume the existing throttling functionality and also include the new auth functionalityconnector.authorization-exempted
config (ref).