Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Configurable Keto variables #870

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,22 @@
public class KetoAuthorizationProvider implements AuthorizationProvider {

private final EnginesApi apiInstance;
private final Map<String, Object> options;

/**
* Initializes the KetoAuthorizationProvider
*
* @param options String K/V pair of options to initialize the provider with. Expects at least a
* "basePath" for the provider URL
*/
public KetoAuthorizationProvider(Map<String, String> options) {
public KetoAuthorizationProvider(Map<String, Object> options) {
if (options == null) {
throw new IllegalArgumentException("Cannot pass empty or null options to KetoAuth");
}
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath(options.get("basePath"));
defaultClient.setBasePath((String) options.get("basePath"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

a better name for the option is authorizationUrl

this.apiInstance = new EnginesApi(defaultClient);
this.options = options;
}

/**
Expand All @@ -57,20 +59,28 @@ public KetoAuthorizationProvider(Map<String, String> options) {
* @return AuthorizationResult result of authorization query
*/
public AuthorizationResult checkAccess(String project, Authentication authentication) {
String email = getEmailFromAuth(authentication);
String subject = (String) this.options.get("subject");
Copy link
Collaborator

Choose a reason for hiding this comment

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

A better name for this option is subjectClaim

Copy link
Collaborator

Choose a reason for hiding this comment

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

rename the option and the variable actually

if ((subject == null) || (subject.isEmpty())) subject = "email";
Copy link
Collaborator

Choose a reason for hiding this comment

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

email should be a constant like DEFAULT_SUBJECT_CLAIM

String subjectValue = getSubjectFromAuth(authentication, subject);
try {
// Get all roles from Keto
List<OryAccessControlPolicyRole> roles =
this.apiInstance.listOryAccessControlPolicyRoles("glob", 500L, 500L, email);
this.apiInstance.listOryAccessControlPolicyRoles("glob", 500L, 500L, subjectValue);
List<String> roleTemplates = (List<String>) this.options.get("roles");

// Loop through all roles the user has
for (OryAccessControlPolicyRole role : roles) {
// If the user has an admin or project specific role, return.
if (("roles:admin").equals(role.getId())
|| (String.format("roles:feast:%s-member", project)).equals(role.getId())) {
return AuthorizationResult.success();
for (String roleTemplate : roleTemplates) {
if (roleTemplate.contains("{PROJECT}")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

PROJECT could be a constant and actually could be in a list of constants that could be replaced in the role templates.

roleTemplate = roleTemplate.replace("{PROJECT}", project);
}
// Loop through all roles the user has
for (OryAccessControlPolicyRole role : roles) {
// If the user has a role matching one of the templates, return.
if ((roleTemplate).equals(role.getId())) {
return AuthorizationResult.success();
}
}
}

} catch (ApiException e) {
System.err.println("Exception when calling EnginesApi#doOryAccessControlPoliciesAllow");
System.err.println("Status code: " + e.getCode());
Expand All @@ -80,27 +90,32 @@ public AuthorizationResult checkAccess(String project, Authentication authentica
}
// Could not determine project membership, deny access.
return AuthorizationResult.failed(
String.format("Access denied to project %s for user %s", project, email));
String.format("Access denied to project %s for user %s", project, subjectValue));
}

/**
* Get user email from their authentication object.
*
* @param authentication Spring Security Authentication object, used to extract user details
* @param subject Subject from the Auth token
* @return String user email
*/
private String getEmailFromAuth(Authentication authentication) {
private String getSubjectFromAuth(Authentication authentication, String subject) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

rename subject to subjectClaim

Jwt principle = ((Jwt) authentication.getPrincipal());
Map<String, Object> claims = principle.getClaims();
String email = (String) claims.get("email");
String subjectValue = (String) claims.get(subject);
Copy link
Collaborator

Choose a reason for hiding this comment

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

subjectValue can then be named subject


if (email.isEmpty()) {
throw new IllegalStateException("JWT does not have a valid email set.");
if (subjectValue.isEmpty()) {
throw new IllegalStateException(String.format("JWT does not have a valid %s.", subject));
}
boolean validEmail = (new EmailValidator()).isValid(email, null);
if (!validEmail) {
throw new IllegalStateException("JWT contains an invalid email address");

if (subject.equals("email")) {
boolean validEmail = (new EmailValidator()).isValid(subjectValue, null);
if (!validEmail) {
throw new IllegalStateException("JWT contains an invalid email address");
}
}
return email;

return subjectValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ public static class AuthorizationProperties {
private String provider;

// K/V options to initialize the provider with
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is no longer a k/V pair since we allow a list for roles. may be worth a conversation

private Map<String, String> options;
private Map<String, Object> options;
}
}
4 changes: 4 additions & 0 deletions core/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ feast:
provider: none
options:
basePath: http://localhost:3000
subject: email
roles:
- role:feast:admin
- role:feast:{PROJECT}-member
Copy link
Member

Choose a reason for hiding this comment

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

I am a bit wary about using complex objects as options. I would strongly prefer to stick to K/V here, so perhaps we can turn roles into a CSV.


grpc:
server:
Expand Down