How to mock a SecurityContext - java

Endpoints with Jersey.
I want to secure an endpoint with a ContainerRequestFilter
#Provider
#Secured
public class AuthorizationRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
final SecurityContext securityContext =
requestContext.getSecurityContext();
//TODO: on logger here...
System.out.printf("Filtering %s request... AuthorizationRequestFilter\n", requestContext.getMethod());
requestContext.getHeaders().add("X-Secured-By", "Jersey >_<");
System.out.printf("SecurityContext: %s (%s).\n", securityContext, securityContext.getAuthenticationScheme());
if (securityContext == null || !securityContext.isUserInRole("privileged")) {
requestContext.abortWith(new UnauthorizedResponse().getResponse());
}
}
}
The annotation #Secured:
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface Secured {}
So I can do this:
#Path("foobar")
public class FooResource {
//...
#Context
SecurityContext securityContext;
//...
#GET
#Secured
#Path(value = "foo")
#Produces(MediaType.APPLICATION_JSON)
public Response getFoo(#Context SecurityContext sc, #Context UriInfo ui, #Context HttpHeaders hh) {
// ...
}
//...
And I'm doing it right (I think), because with my test I don't even pass through the getFoo endpoint but is the ContainerRequestFilter that kicks me out. In fact I receive this (the "X-Secured-By" header is hand-made):
Headers: {X-Secured-By=[Jersey >_< kicked you out!], Content-Length=[97], Date=[Wed, 03 Dec 2014 17:46:50 GMT], Content-Type=[application/json], X-Powered-By=[Jersey ^_^]}
Response: InboundJaxrsResponse{ClientResponse{method=GET, uri=http://localhost:9998/urler/test, status=401, reason=Unauthorized}}
Now it would be nice to mock the SecurityContext.
This is what I'm doing... and if I'm here, it's obviously silly and/or wrong.
public class UrlerResourceTest extends JerseyTest {
//....
#Override
public TestContainerFactory getTestContainerFactory() {
GrizzlyTestContainerFactory grizzlyTestContainerFactory = new GrizzlyTestContainerFactory();
System.out.printf("The GrizzlyTestContainerFactory: %s ", grizzlyTestContainerFactory);
// just for debugging...
return grizzlyTestContainerFactory;
}
#Test
public void testSecuredEndpoint() throws JSONException {
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.isUserInRole(anyString())).thenReturn(true);
Mockito.when(securityContext.getAuthenticationScheme()).thenReturn("Just Mocking...");
ReflectionTestUtils.setField(resource, "securityContext", securityContext, SecurityContext.class);
final Response response = target("foobar")
.path("foo")
.request(MediaType.APPLICATION_JSON)
.get();
System.out.println(getFormattedStringResponseInfo(response));
JSONObject entity = new JSONObject(response.readEntity(String.class));
assertTrue(entity.get("secured").equals(true));
assertTrue(response.getHeaders().containsKey("X-Secured-By"));
assertEquals(Status.OK.getStatusCode(), response.getStatus());
}
How can I mock the SecurityContext in my tests?
Thank you so much in advance.

Disclaimer: I'm not really a Mockito user, but from what I understand, mocking is used for situations where you have injected class dependencies (fields), and you mock those dependencies. In which case you still need to set the field with the mocked object. For example
public class TestClass {
TestService testService;
public void doTest() {
System.out.println(testService.getString());
}
public void setTestService(TestService testService) {
this.testService = testService;
}
}
public class TestService {
public String getString() {
return "Hello world";
}
}
#Test
public void toTest() {
TestService testService = Mockito.mock(TestService.class);
Mockito.when(testService.getString()).thenReturn("Hello Squirrel");
TestClass testClass = new TestClass();
testClass.setTestService(testService);
testClass.doTest();
}
You can see we are setting the the TestService in the TestClass with the mocked object. It's not greatest example, as we could simple instantiate TestService, but it shows, from my understanding, how the mocking should work.
That being said, I don't see how it is possible to do this with the AuthorizationRequestFilter, as it's handled by the test container, and we are not instantiating it for a unit test. Even if we were, it would seem intrusive (and redundant) to add a SecurityContext field.
So without a full integration test, where we are starting the server, and using the server's authentication capabilities, it will be difficult to handle the SecurityContext per this use case, as the SecurityContext is created by the container, taking information from the underlying servlet containers authentication mechanism.
One way you can achieve this though (which IMO doesn't seem very elegant - but works), without a full integration test, is to create a a filter which performs before your AuthorizationRequestFilter, and set the SecurityContext from there. Testing aside, this is actually pretty common in cases where we need to implement outr own custom authentication mechanism.
An example of how you might do this for your unit test, might be something like:
public class UrlerResourceTest extends JerseyTest {
...
#Override
public Application configure() {
return new ResourceConfig(FooResource.class)
.register(AuthorizationRequestFilter.class)
.register(AuthenticationFilter.class);
}
#Provider
#Priority(Priorities.AUTHENTICATION)
public static class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return "Stackoverflow";
}
};
}
#Override
public boolean isUserInRole(String string) {
return "privileged".equals(string);
}
#Override
public boolean isSecure() { return true; }
#Override
public String getAuthenticationScheme() { return "BASIC"; }
});
}
}
...
}
This filter will perform before the AuthorizationRequestFilter because of the #Priority annotation. We've set it to Priorities.AUTHENTICATION which will before before any other filter without such annotation. (See Priorities API and Priorities with Jersey. Also the SecurityContext will be passed along between filters and also be injected into your resource class.
As I said, I don't think this is very elegant to have to create another filter, but it works for this purpose. Also I am not too familiar with the Jersey Test Framework, as I'm still beginning with it, but there are many configuration options for deployment within a servlet context. I don't know if we can configure the needed authentication mechanism for this case, but it might be something worth looking into.
Edit: In the beginning I explained about setting the field for the test object, but we can also pass the mocked object to a method. For example we could mock the ContainterRequestContext in the filter method, and call filter ourselves, passing the mocked ContainerRequestContext. But this is only useful when we are actually unit testing the filter class and instantiating it ourselves, which is not the case here.

Related

Access resource method arguments from inside a Jersey filter or interceptor. Or use AOP with resource method

I'm trying to enrich the SLF4J MDC on each request with the user's ID. The problem is that the ID can be passed in many ways, sometimes as a path parameter, sometimes in the body, and sometimes injected by a custom ValueFactoryProvider that first decrypts it.
If I could somehow access all the injected (i.e. already deserialized) parameter values, I could handle all these cases easily.
E.g.
For a resource such as:
#GET
//#Encrypted params are injected by a custom ValueFactoryProvider
public Something getSomething(#Encrypted("userId") String userId) {
return ...;
}
#POST
public Something getSomething(#RequestBody RequestWithUserId requestWithUserId) {
return ...;
}
I could have a filter such as:
public class MdcFilter implements ContainerRequestFilter, ContainerResponseFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Method theMethod = resourceInfo.getResourceMethod();
for (Parameter parameter : theMethod.getParameters()) {
//Deal with the #Encrypted case
if (parameter.isAnnotationPresent(Encrypted.class) && parameter.getAnnotation(Encrypted.class).value().equals("userId")) {
MDC.put("userId", somehowGetTheValue());
}
//Deal with the #RequestBody case
if (parameter.isAnnotationPresent(RequestBody.class) && parameter.getType().equals(RequestWithUserId.class)) {
MDC.put("userId", ((RequestWithUserId)somehowGetTheValue()).getUserId());
}
... //other possibilities
}
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
MDC.clear();
}
}
But I don't see a way to implement somehowGetTheValue either from a ContainerRequestFilter an interceptor or anything else...
Jersey uses HK2 under the hood for dependency injection. And HK2 has AOP support. One option for your use case would be use this AOP support. All you need to do is implement a MethodInterceptor and an InterceptionService. In the MethodInterceptor, you can get all the arguments from the MethodInvocation and you can get parameter annotation from the Method
class MyMethodInteceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// do your logging or whatever with the args.
// invoke method and get return value.
Object returnValue = invocation.proceed();
// if you want to do something with the return
// value before returning it, you can.
return returnValue;
}
}
To use the interceptor, you configure the InterceptionService.
public class MyInterceptionService implements InterceptionService {
private final static MethodInterceptor METHOD_INTERCEPTOR
= new MyMethodInterceptor();
private final static List<MethodInterceptor> METHOD_LIST
= Collections.singletonList(METHOD_INTERCEPTOR);
#Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
#Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
// you implement shouldIntercept
if (shouldIntercept(method)) {
return METHOD_LIST;
}
return null;
}
#Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
return null;
}
}
You determine which method should be intercepted in the getMethodInterceptors() method. If the method should be intercepted, then return a list of interceptors, otherwise return null. A common way of handling this is to create a custom annotation and just annotate the method. The in the above method, just check
if (method.isAnnotationPresent(YourAnno.class)) {
return METHOD_LIST;
}
To make it all work, you just need to register the InteceptionService with HK2. You can do that in an AbstractBinder, which is what is used in a Jersey app to configure your DI.
ResourceConfig config = new ResourceConfig();
config.register(new AbstractBinder() {
#Override
protected void configure() {
bind(MyInterceptionService.class)
.to(InterceptionService.class)
.in(Singleton.class);
}
});
You can see a complete example in this GitHub repo. There is also an official example in the HK2 site. Just see "AOP support" the link at the top of the post.
You can get it like this
StringWriter stringWriter = new StringWriter();
IOUtils.copy(new InputStreamReader(requestContext.getEntityStream()), stringWriter);
System.out.println(stringWriter.toString());// String representation of the payload
requestContext.setEntityInputStream(new ByteArrayInputStream(requestEntity));
Basically the idea is to copy the stream and do any processing and then set the stream back. Because if you don't do that, then in your controller method you would get null, becuase the stream was already read.

Creating and injecting a per request scoped variable

I would like to have a variable that follows along the full lifecycle of a request in java EE.
For example it could be for a logging function, so that I can filter all log entries by request.
The key part that I want to get at is that it must be relatively easy to implement in an already existing application so if possible some sort of dependency injection that gets the variable related to the specific request.
I've tried injectiong a #RequestScoped variable, but it doesn't work since it is only scoped to the container. I would need to be able to inject the same object to different containers. Is this at all possible?
EDIT: I want something along the lines of this:
#RequestScoped
public class RequestVariables {
public String id;
}
#Stateless
public class Logger {
#Inject
private RequestVariables requestVariables;
public void log(String message) {
System.out.println(requestVariables.id + ":" + message);
}
}
#Stateless
public class Service {
#Inject
private Logger logger;
#Inject
private RequestVariables requestVariables;
public void save(String data) {
logger.log("Save");
session.save(data + requestVariables.id); //Maybe add request parameter to save aswell
}
}
public class API {
#Inject
private Service service;
#Inject
private Logger logger;
#Inject
private RequestVariables requestVariables;
#Path("/1")
#GET
public Response get(#QueryParam("data") String data) {
requestVariables.id = UUID.randomUUID().toString()
service.save(data);
logger.log("Get");
return Response.status(204).build();
}
}
Currently this is what I have experimented with:
#RequestScoped
public class RequestScope {
private int test = 0;
public RequestScope(int test) {
this.test = test;
}
public RequestScope(){}
public int getTest() {
return test;
}
public void setTest(int test) {
this.test = test;
}
}
#Provider
public class RequestScopeFilter implements ContainerRequestFilter {
#Inject
private javax.inject.Provider<RequestScope> requestScopeProvider;
#Context
private HttpServletRequest request;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestScopeProvider.get().setTest(42);
request.setAttribute("test", "superTest");
}
}
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
#TransactionAttribute(value=TransactionAttributeType.NOT_SUPPORTED)
public class Service {
#Context
private HttpServletRequest httpServletRequest;
#Inject
private Provider<RequestScope> requestScopeProvider;
public void test() {
RequestScope scope = requestScopeProvider.get();
String test = (String)httpServletRequest.getAttribute("test");
}
}
So when I get the scope from my service then it is a new object with test set to 0, and then it throws an NPE since httpServletRequest is null
option #1
Implement an Interceptor and set the request id as HttpServletRequest attribute:
#AroundInvoke
public Object setRequestId(InvocationContext ic) throws Exception {
HttpServletRequest request = [..] // getHttpServletRequest(ic);
request.setAttribute("request-id", UUID.randomUUID().toString());
return ic.proceed();
}
Then use HttpServletRequest everywhere you need it
#Context
private HttpServletRequest httpRequest;
option #2
If want just to filter your logs by an unique id, you can configure your Logger to print the thread name: [%t]
Example: Log4j PatternLayout
option #3
Use a custom java bean to encapsulate the request data (query param, request id etc.) and pass this bean across your application services.
public class API {
#Inject
private Service service;
#Path("/1")
#GET
public Response get(MyCustomRequestBean data) {
service.doSomejob(data);
return Response.status(204).build();
}
}
Set the request id and query param in ParamConverter:
Jax-RS ParamConverter - ParamConverterProvider method return type mismatch
You can inject a provider in your service:
#Inject
Provider<RequestVariables> vars
And then call get () to get the instance. If you try to get () in a thread outside a request scope context you'll get an exception. I would however try to structure in a way that would not allow this to happen
A solution that I found is to use ThreadLocal variables. It seems rather dirty, but it works since each request is executed on it's own thread(as far as I am aware). So this is what I got:
public class RequestScope {
private static final ThreadLocal<String> id = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
public static String get() {
return id.get();
}
}
With that I can also easily exchange the ThreadLocal to return something more specific if so desired.
And I can get the variables from pretty much anywhere, assuming that the request is not starting a different thread

How to embed request level objects to #Context

In embedded Jersey I can register a Binder to put in some resources that I can eventually access using #Context
However, those things I put in are more global and not on a per request level. I do know I can do it with some property mapping, but I would rather do it through #Context with class like Response foo(#Context HttpServletRequest)
I tried the setRequestScopedInitializer() but it does not put them in as expected and following their example with the Ref gives me a null pointer exception
Is there any way of doing this?
Here's how I eventually did it, but I don't like that I used a named property
RoutingContext was the type I wanted to inject
public class RoutingContextFactory implements
Supplier<RoutingContext> {
#Inject
private ContainerRequest request;
#Override
public RoutingContext get() {
return (RoutingContext) request.getProperty(RoutingContext.class.getName());
}
}
My binder
public class MyBinder extends AbstractBinder {
#Override
protected void configure() {
bindFactory(RoutingContextFactory.class)
.to(RoutingContext.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
}
}
Initialized by
final ResourceConfig resourceConfig = ResourceConfig.forApplicationClass(applicationClass);
resourceConfig.register(new MyBinder());
Loaded by
final ContainerRequest request = new ContainerRequest(...
request.setProperty(RoutingContext.class.getName(), routingContext);
Used by
#GET
#Produces(MediaType.TEXT_PLAIN)
public String hello(
#Context final RoutingContext routingContext) {
return "Hello"
+ routingContext;
}
I still wish there was a way for me to just go request.register(routingContext). I opened up https://github.com/jersey/jersey/issues/3682 for this.

how to inject headers in a `#context HttpServletRequest`?

Let's say I have this code:
#ApplicationPath("...")
public class MyApp extends ResourceConfig {
public SalesLayerApplication() {
this.register(HeaderInjecterFilter.class);
this.register(Test.class);
}
}
#PreMatching
public class HeaderInjecterFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext crc) throws IOException {
crc.getHeaders().add("foo", "bar");
}
}
#Path("/test")
public class Test {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String dump(#Context final HttpServletRequest request) {
return request.getHeader("foo");
}
}
I was expecting to call the rest entry point /test and to retrieve the string bar.
But all I see is null
If I use #HeaderParam("foo") I correctly retrieve the variable, but I need to access throug the #Context HttpServletRequest.
Why would you expect that adding headers to the ContainerRequestContext would also add it to the HttpServletRequest? These are completely unrelated entities. Try injecting HttpHeaders or you can also inject the ContainerRequestContext directly.

How to Optionally Protect a Resource with Custom Dropwizard Filter

I'm using Dropwizard 0.9.2 and I want to create a resource that requires no authentication for GET and requires basic authentication for POST.
I have tried
#Path("/protectedPing")
#Produces(MediaType.TEXT_PLAIN)
public class ProtectedPing {
#GET
public String everybody() {
return "pingpong";
}
#PermitAll
#POST
public String authenticated(){
return "secret pingpong";
}
with
CachingAuthenticator<BasicCredentials, User> ca = new CachingAuthenticator<>(environment.metrics(), ldapAuthenticator, cbSpec);
AdminAuthorizer authorizer = new AdminAuthorizer();
BasicCredentialAuthFilter<User> bcaf = new BasicCredentialAuthFilter.Builder<User>().setAuthenticator(ca).setRealm("test-oauth").setAuthorizer(authorizer).buildAuthFilter();
environment.jersey().register(bcaf);
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
environment.jersey().register(new ProtectedPing());
This seems to result in all requests to "/protectedPing" requiring basic auth.
In Dropwizard 0.9.2 the documentation says to create a custom filter if I have a resource that is optionally protected. I'm assuming I need to do that, but I don't know where to start, or if that I what I actually need to do.
this is more of a jersey problem than a dropwizard problem. You can have a look here: https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Essentially what you want is:
Create an annotation that indicates that you want to test for authentication (e.g. #AuthenticatePost)
Create the resource and annotate the correct method with #AuthenticatePost
Create your authentication filter (probably kind of like what you did above).
In the dynamic feature, test for the annotation to be present on the passed in resource. This will hold true for post, false for get. Then register the AuthenticationFilter directly on the resource method instead of globally on the resource.
This would be a semi-complete example of how I would solve this:
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if(resourceInfo.getResourceMethod().getAnnotation(AuthenticateMe.class) != null ) {
context.register(MyAuthFilter.class);
}
}
public class MyAuthFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// do authentication here
}
}
public #interface AuthenticateMe {
}
#Path("myPath")
public class MyResource {
#GET
public String get() {
return "get-method";
}
#POST
#AuthenticateMe
public String post() {
return "post-method";
}
}
}
Note, the DynamicFeature checks that the Authenticate Annotation is present, before registering the authentication with the feature context.
I hope that helps,
let me know if you have any questions.

Categories