Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock a SecurityContext

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.

like image 736
zeroed Avatar asked Dec 03 '14 18:12

zeroed


People also ask

Does JUnit have mocking?

While doing unit testing using junit you will come across places where you want to mock classes. Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls.


1 Answers

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.

like image 195
Paul Samsotha Avatar answered Oct 05 '22 09:10

Paul Samsotha