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

Fix and refactor role based filter to allow CORS #99

Merged
merged 1 commit into from
Nov 18, 2016
Merged

Conversation

garyluoex
Copy link
Collaborator

@garyluoex garyluoex commented Nov 17, 2016

  • Fix RoleBasedAuthFilter to bypass OPTIONS request for CORS
  • Discovered a bug where user_roles is declared but unset still reads as a list with empty string (included a temporary fix by deleting the variable declaration)
  • Refactored RoleBasedAuthFilter and RoleBasedAuthFilterSpec for better testing

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

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

Needs CHANGELOG entry

return allowedUserRoles.isEmpty() ? true : allowedUserRoles.stream().anyMatch(securityContext::isUserInRole);
}

/**\
Copy link
Collaborator

Choose a reason for hiding this comment

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

s// (ie. typo... \ shouldn't be there)

}
}

/**
* Request authorization logic.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be good to highlight what that logic is, rather than forcing the user to read the code to understand it. In particular: If the HTTP method is OPTIONS or if the user has a role that's in the configured allowed list

}

/**\
* By default, returns true to bypass the filter if the request is a Http OPTIONS method to allow CORS.
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to have byDefault, since this method (being private) can't be overridden.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Making it protected to allow for override

// Release the test web container
jtb.tearDown()
roleBasedAuthFilter = new RoleBasedAuthFilter()
SYSTEM_CONFIG.setProperty(SYSTEM_CONFIG.getPackageVariableName("user_roles"), "foo,bar");
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to make sure this gets cleaned up and re-set to it's original value. Property leaks are SUCH a pain to find.

}
ContainerRequestContext containerRequestContext;
SecurityContext securityContext;
RoleBasedAuthFilter roleBasedAuthFilter;
Copy link
Collaborator

Choose a reason for hiding this comment

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

No semicolons in groovy

def "GET request is not authorized if the user is not in an allowed role"() {
when: "received a request and that the user is not in the authorized roles"
containerRequestContext.getMethod() >> "GET"
securityContext.isUserInRole() >> false
Copy link
Collaborator

Choose a reason for hiding this comment

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

This mock won't work as you're expecting. It shouldn't ever trigger because it's not valid to call that method with no parameters. If you want the mock to trigger on all calls to that method, you should use the wildcard parameter syntax: securityContext.isUserInRole(*_)

when: "received a request and that the user is not in the authorized roles"
containerRequestContext.getMethod() >> "GET"
SYSTEM_CONFIG.clearProperty(SYSTEM_CONFIG.getPackageVariableName("user_roles"))
securityContext.isUserInRole() >> false
Copy link
Collaborator

Choose a reason for hiding this comment

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

See previous note on making this trigger the way I think you want it to.

securityContext.isUserInRole() >> false

then: "the request is not authorized"
roleBasedAuthFilter.isAuthorized(containerRequestContext) == false
Copy link
Collaborator

Choose a reason for hiding this comment

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

These assertions can be simplified, since they are all pretty much boolean responses. Instead of == false, you can just prepend ! to the call, and you don't need == true if you're asserting truth.

containerRequestContext.getMethod() >> "GET"
securityContext.isUserInRole() >> false

then: "the request is not authorized"
Copy link
Collaborator

Choose a reason for hiding this comment

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

None of these tests are "stimulus / assertion on the state of the world" tests, but instead they are all "given the state of the world, assert a behavior", which lends itself much more to the given / expect style of tests, rather than the when / then style. So, it would be good to convert all of these tests to given / expect

RoleBasedAuthFilter roleBasedAuthFilter = new RoleBasedAuthFilter()
def "OPTIONS request is authorized which bypasses the filter"() {
when: "received an OPTIONS request and that the user is in the authorized roles"
securityContext.isUserInRole() >> false
Copy link
Collaborator

Choose a reason for hiding this comment

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

May not be triggering when you expect it to

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

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

CHANGELOG still needed

*/
protected boolean isUserInRole (List<String> allowedUserRoles, ContainerRequestContext containerRequestContext) {
SecurityContext securityContext = containerRequestContext.getSecurityContext();
return allowedUserRoles.isEmpty() ? true : allowedUserRoles.stream().anyMatch(securityContext::isUserInRole);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can simplify this using || like so: allowedUserRoles.isEmpty() || allowedUserRoles.stream().anyMatch(securityContext::isUserInRole);

}

def cleanup() {
// Release the test web container
jtb.tearDown()
SYSTEM_CONFIG.clearProperty(SYSTEM_CONFIG.getPackageVariableName("user_roles"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can't assume that this was unset when the test started. We need to capture the value of the property (as a String) in the setup, and then use that captured value to reset(capturedValue) in the cleanup.

Copy link
Collaborator

@cdeszaq cdeszaq left a comment

Choose a reason for hiding this comment

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

Just those last three small things and this will be ready!


try {
oldUserRoles = Optional.of(SYSTEM_CONFIG.getStringProperty(userRolesSetting))
} catch(SystemConfigException e) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

For catch blocks where we're ignoring the exception, it's best to call the exception ignored (or something like that) to make it clear that we're intentionally not doing anything with the exception.

@@ -15,49 +18,71 @@ import javax.ws.rs.core.SecurityContext
@Timeout(30)
class RoleBasedAuthFilterSpec extends Specification {

JerseyTestBinder jtb
private static final SystemConfig SYSTEM_CONFIG = SystemConfigProvider.getInstance()
private static final String userRolesSetting = SYSTEM_CONFIG.getPackageVariableName("user_roles")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this variable is static, it should be in STATIC_CASE

// Release the test web container
jtb.tearDown()
// Restore pre-test setting value if exists
oldUserRoles.ifPresent({userRoles -> SYSTEM_CONFIG.resetProperty(userRolesSetting, userRoles)})
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to clear the setting if the Optional is empty, which this doesn't do.

@cdeszaq
Copy link
Collaborator

cdeszaq commented Nov 17, 2016

👍

Copy link
Collaborator

@RVRSKumar RVRSKumar left a comment

Choose a reason for hiding this comment

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

Just minor comments

@@ -28,12 +28,16 @@ Current

### Fixed:

- [Fix and refactor role based filter to allow CORS](https://github.com/yahoo/fili/pull/99)
* Fix `RoleBasedAuthFilter` to bypass `OPTIONS` request for CORS
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we expand what CORS is?

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 changelog is not the place to explain things, google CORS will give plenty of answers. What do you think? @cdeszaq

* @return true if request is authorized, false otherwise
*/
private boolean isAuthorized(ContainerRequestContext containerRequestContext) {
return isBypassMethod(containerRequestContext) || isUserInRole(getAllowedUserRoles(), containerRequestContext);
Copy link
Collaborator

Choose a reason for hiding this comment

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

In getAllowedUserRoles() what is the list that is returned when the bard__user_roles variable is uncommented. Is it not an empty list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

returns it is a list with an empty string

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 a good point. It might not be a bad idea to add this behavior description to the getList* methods in the SystemConfig interface (and possibly add a test asserting the behavior).

That said, I'm also OK making this piece an issue and coming back to it.

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 was thinking we should just make it a bug issue and fix it later on, because it should be obvious that this behavior is undesirable.

@RVRSKumar
Copy link
Collaborator

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants