Skip to content
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

Merged
merged 23 commits into from
Oct 26, 2023
Merged

Default AuthZ Provider Skeleton Code #45

merged 23 commits into from
Oct 26, 2023

Conversation

aayustark007-fk
Copy link
Collaborator

@aayustark007-fk aayustark007-fk commented Sep 3, 2023

  • Implemented config driven roles and role binding
  • Linking to the AuthorizationProvider interface and implemented isAuthorised method
  • Added some basic roles and role bindings

@aayustark007-fk aayustark007-fk changed the title Default AuthZ Provider Skeleton Code <wip> Default AuthZ Provider Skeleton Code Sep 3, 2023
@codecov-commenter
Copy link

codecov-commenter commented Sep 3, 2023

Codecov Report

Attention: 29 lines in your changes are missing coverage. Please review.

Comparison is base (c066395) 52.58% compared to head (578ab12) 53.55%.

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     
Files Coverage Δ
...m/flipkart/varadhi/auth/AuthorizationProvider.java 0.00% <ø> (ø)
...n/java/com/flipkart/varadhi/auth/ResourceType.java 0.00% <0.00%> (ø)
...art/varadhi/auth/DefaultAuthorizationProvider.java 93.47% <93.47%> (ø)
...in/java/com/flipkart/varadhi/web/AuthHandlers.java 0.00% <0.00%> (ø)
...java/com/flipkart/varadhi/auth/ResourceAction.java 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@github-actions
Copy link

github-actions bot commented Sep 3, 2023

Test Results

90 tests   90 ✔️  42s ⏱️
25 suites    0 💤
25 files      0

Results for commit 398a342.

♻️ This comment has been updated with latest results.

// 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);
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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"
Copy link
Collaborator

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.

Copy link
Collaborator Author

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);
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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:

  1. 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.
  2. 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?

Copy link
Collaborator Author

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);
Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -1,6 +1,7 @@
package com.flipkart.varadhi.auth;

public enum ResourceType {
ROOT("root"),
Copy link
Collaborator Author

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

Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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.

@aayustark007-fk aayustark007-fk changed the title <wip> Default AuthZ Provider Skeleton Code Default AuthZ Provider Skeleton Code Oct 13, 2023
private volatile boolean initialised = false;

@Override
public Future<Boolean> init(AuthorizationOptions authorizationOptions) {
Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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 ?

// 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)) {
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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)));
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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());
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

server/src/main/resources/configuration.yml Show resolved Hide resolved
private volatile boolean initialised = false;

@Override
public Future<Boolean> init(AuthorizationOptions authorizationOptions) {
Copy link
Collaborator

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"),
Copy link
Collaborator

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;
Copy link
Collaborator

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 ?

Copy link
Collaborator Author

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()) {
Copy link
Collaborator

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 ?

Copy link
Collaborator

@kmrdhruv kmrdhruv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aayustark007-fk aayustark007-fk merged commit f2179e3 into master Oct 26, 2023
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants