-
Notifications
You must be signed in to change notification settings - Fork 3
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
Default AuthZ Provider Skeleton Code #45
Conversation
aayustark007-fk
commented
Sep 3, 2023
•
edited
edited
- Implemented config driven roles and role binding
- Linking to the AuthorizationProvider interface and implemented isAuthorised method
- Added some basic roles and role bindings
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #45 +/- ##
============================================
+ Coverage 52.58% 53.55% +0.97%
- Complexity 226 245 +19
============================================
Files 72 72
Lines 1394 1449 +55
Branches 83 85 +2
============================================
+ Hits 733 776 +43
- Misses 631 640 +9
- Partials 30 33 +3
☔ View full report in Codecov by Sentry. |
server/src/main/java/com/flipkart/varadhi/auth/DefaultAuthorizationProvider.java
Show resolved
Hide resolved
// TODO(aayush): need better way to do this? | ||
Map<ResourceType, String> resultMap = new HashMap<>(); | ||
String[] segments = resource.split("/"); | ||
return resolve(ResourceType.ORG, action, List.of(segments), resultMap); |
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.
Since format is fixed and resolve also depends on the format, would it be better to do simple parsing instead of recursive call ?
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.
agree, will make the changes based on the outcome of #45 (comment)
|
||
authorization: | ||
superUsers: [ "thanos" ] | ||
providerClassName: | ||
providerOptions: | ||
providerClassName: "com.flipkart.varadhi.auth.DefaultAuthorizationProvider" |
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.
fine for POC, but for prod it might be better to extract these to authz specific file and have a pointer in the main config file.
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.
sure will do that
@Override | ||
public Future<Boolean> isAuthorized(UserContext userContext, ResourceAction action, String resource) { | ||
// parse the resource path based on the action and return the final resourceIDs for each resourceType | ||
Map<ResourceType, String> resourceTypeToResourceId = getResourceIds(action, 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.
would it be more beneficial to evaluate it from root to leaf or opposite, thoughts ?
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.
Currently we iterate over map entries, so the evaluation order here is undefined.
One problem them I'm facing is that in case of project, how do I figure out the parent entity ID?
Some approaches I have in mind:
- Pass only the project_id as resource (as being currently done in RequiredAuthorization) and the Authorization Provider has the required mapping to the parent entity. So, when we fetch the role_bindings for a given resource_id, we get the role_bindings at that resource_id level along with the role_bindings at the parent level till the root node.
- Or, since this is a Default barebones impl, we just do away with the requirement of inheriting permissions and check only at the resource_ids being passed.
Now coming to the original question, what would be better? Root to Leaf or opposite?
I'd say, considering the points above, opposite evaluation will be much better as we are mostly being passed the last set of identifiers through which we will mostly move up the hierarchy. Evaluating Root to Leaf will involve the extra work of figuring out the entire hierarchy at once and then evaluating.
What are your thoughts?
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 the code, we are evaluating in lazy manner from leaf to root node
} | ||
|
||
private boolean doesActionBelongToRole(String roleId, ResourceAction action) { | ||
return configuration.getRoles().getOrDefault(roleId, new ArrayList<>()).contains(action); |
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 debugging purpose, it would be good maintain (log) the information which node and which role returned true for authorised call.
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.
done
…into default-authz
@@ -1,6 +1,7 @@ | |||
package com.flipkart.varadhi.auth; | |||
|
|||
public enum ResourceType { | |||
ROOT("root"), |
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.
required for bootstrapping and org create
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.
How is boot strapping being done ? Also let's finalise how we want to provide org creation ?
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.
Idea is that when there is no Org defined, we still need a resource node to hold the bootstrap role bindings. Thats why a ROOT is defined which will hold role bindings required for org creation and role assignment. We can also drive super-users by adding role bindings to this node.
private volatile boolean initialised = false; | ||
|
||
@Override | ||
public Future<Boolean> init(AuthorizationOptions authorizationOptions) { |
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.
Was planning to read the file as json object and pass to the providers. But, decided against it because I think it would be better to have control at the provider end in case they wish to specify the configuration as JSON instead of YAML or even interpret the configFile path as an URI and fetch the config over the internet or file server.
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.
with this we can also give configFile as:
configFile: "https://raw.githubusercontent.com/acme-org/acme-project/customconfig.json"
And the provider can then make an http call to fetch and parse the config.
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 we can stay with single file format for entire project instead of giving the flexibility. Thoughts ?
However loading from different location is good to have feature. We can look to enhance yaml loader to support these, may be at later point ?
entities/src/main/java/com/flipkart/varadhi/auth/DefaultAuthorizationConfiguration.java
Outdated
Show resolved
Hide resolved
server/src/main/java/com/flipkart/varadhi/auth/DefaultAuthorizationProvider.java
Show resolved
Hide resolved
// build the list in reverse order specified: ROOT -> ORG -> TEAM -> PROJECT -> TOPIC|SUBSCRIPTION|QUEUE | ||
List<Pair<ResourceType, String>> resourceIdTuples = new ArrayList<>(); | ||
// handle leaf node case | ||
if (isActionOnLeafNode(action)) { |
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.
does leaf node need to be special case ?
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.
Not a special case, we want to know if there are any topic or subscription id provided in the resource path. Eg: Say in case of project create, there will be no topic or subscription id present so no leaf node is added in the list of tuples.
if (isActionOnLeafNode(action)) { | ||
resourceIdTuples.add(Pair.of(action.getResourceType(), getLeaf(segments))); | ||
} | ||
resourceIdTuples.add(Pair.of(ResourceType.PROJECT, getProject(segments))); |
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.
does empty values needs to be populated for non-exising node types in the path ? This can be what is given in the input and also avoid isNotBlank() check in isAuthorized() above ?
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 we can omit adding keys which are blank. Will make the change
|
||
private boolean doesActionBelongToRole(String subject, String roleId, ResourceAction action) { | ||
log.debug("Evaluating action [{}] for subject [{}] against role [{}]", action, subject, roleId); | ||
boolean matching = configuration.getRoles().getOrDefault(roleId, List.of()).contains(action.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.
Can role be implemented as Set<>, instead of List<> of actions ?
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.
done
private volatile boolean initialised = false; | ||
|
||
@Override | ||
public Future<Boolean> init(AuthorizationOptions authorizationOptions) { |
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 we can stay with single file format for entire project instead of giving the flexibility. Thoughts ?
However loading from different location is good to have feature. We can look to enhance yaml loader to support these, may be at later point ?
@@ -1,6 +1,7 @@ | |||
package com.flipkart.varadhi.auth; | |||
|
|||
public enum ResourceType { | |||
ROOT("root"), |
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.
How is boot strapping being done ? Also let's finalise how we want to provide org creation ?
* If specified as true, then the default implementation via {@link com.flipkart.varadhi.auth.DefaultAuthorizationProvider} is used.<br/> | ||
* {@link AuthorizationOptions#providerClassName} value is ignored. | ||
*/ | ||
private Boolean useDefaultProvider; |
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.
is useDefaultProvider redundant given that provierClassName exists ? Also let's see how should we enable superUsers ?
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 we can remove the useDefaultProvider flag since providerClassName will be enough to flag that case
return new AuthorizationHandlerBuilder(configuration.getAuthorization() | ||
.getSuperUsers(), authorizationProvider); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
private AuthorizationProvider getAuthorizationProvider(ServerConfiguration configuration) { | ||
if (configuration.getAuthorization().getUseDefaultProvider()) { |
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.
any specific reason to special case for this when compared to others ?
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.
LGTM