开发者

Playframework Secure module: how do you "log in" to test a secured controller in a FunctionalTest?

开发者 https://www.devze.com 2023-02-28 19:02 出处:网络
EDIT: I\'m using Play! version 1.2 (production release) I want to test controller actions that are secured by Secure module

EDIT: I'm using Play! version 1.2 (production release)

I want to test controller actions that are secured by Secure module class, so I need to log in prior to testing my controller (otherwise I will be re开发者_高级运维directed to the login page).

I've tried to log in prior to calling a secured action. Here's what my FunctionalTest looks like:

@Test
public void someTestOfASecuredAction() {
    Map<String, String> loginUserParams = new HashMap<String, String>(); 
    loginUserParams.put("username", "admin"); 
    loginUserParams.put("password", "admin");

    // Login here so the following request will be authenticated:
    Response response = POST("/login", loginUserParams);

    // The following is an action that requires an authenticated user:
    Map<String, String> params;
    params.put("someparam", "somevalue");
    response = POST("/some/secured/action", params);

    assertIsOk(response); // this always fails because it is a 302 redirecting to /login
}

Stepping through the code, I've verified that the login post works - it causes a redirect response with location set to the home page (which indicates a successful login).

But then in the subsequent call to a secured action, I am always redirected to the "/login" page - indicating that my previous login did not stick for the second POST request.

Looking into the source code of FunctionalTest I saw there was an @Before interceptor that clears all cookies. I tried overriding this intercepter in my own intermediary superclass (to preserve the cookies), but that didn't work either.

EDIT: I was confusing the play.mvc.Before interceptor with org.junit.Before - the former for use with Play! controllers, the latter for JUnit tests. The @Before in the FuncitonTest is a JUnit interceptor, so it should have any affect on cookies (since it gets run once prior to the test being run).

I do not want to have to write a Selenium test for every secured action - since almost all will be secured. Is there a way to "fool" the Secure module into believing you're authenticated? Or maybe some other very obvious way for handling this (seemingly common) scenario in a FunctionalTest?

Thanks in advance,

Mark

EDIT: Working code, Codemwnci's answer marked as correct

Codemwnci's answer is correct. Here is my workaround for preserving the cookies from one request to the next:

@Test
public void someTestOfASecuredAction() {
    Map<String, String> loginUserParams = new HashMap<String, String>();
    loginUserParams.put("username", "admin");
    loginUserParams.put("password", "admin");
    Response loginResponse = POST("/login", loginUserParams);

    Request request = newRequest();
    request.cookies = loginResponse.cookies; // this makes the request authenticated
    request.url = "/some/secured/action";
    request.method = "POST";
    request.params.put("someparam", "somevalue");
    Response response = makeRequest(request);
    assertIsOk(response); // Passes!
}


I think there must be a misunderstanding of what the @Before interceptor does. It executes before your test is executed. If your test then logs you in, then this event happens AFTER the @Before code has been executed and the results of the secure module should be saved to the Cookie.

Therefore, I can only assume that the Cookie is not being sent with the following request, therefore, I would suggest trying the following...

get the cookie used by the secure cookie from the Response object immediately following your login. Create a Request object and set the Cookie to the request object, then call your POST method, passing in your request object.

I have not tested this code, so not sure how it is going to react to mixing a pre-built request object, and passing in a URL, but not sure what else to suggest.


I had the same problem, but in tests with Play 2.0.4.

I solved the problem by following Seb and Codemwnci answers and I built, checking API, the following solution:

@Test
public void listSomething() {
    running(fakeApplication(inMemoryDatabase()), new Runnable() {
        @Override
        public void run() {
            // LOGIN
            final Map<String, String> data = new HashMap<String, String>();
            data.put("email", "user@domain.com");
            data.put("password", "userpassword");

            Result result = callAction(
            controllers.routes.ref.Application.authenticate(),
            fakeRequest().withFormUrlEncodedBody(data));

            // RECOVER COOKIE FROM LOGIN RESULT
            final Cookie playSession = play.test.Helpers.cookie("PLAY_SESSION",
                                                                result);

            // LIST SOMETHING (using cookie of the login result)
            result = callAction(controllers.routes.ref.Application.list(), 
                                fakeRequest().withCookies(playSession));

            /* 
             * WAS RECEIVING 'SEE_OTHER' (303) 
             * BEFORE RECOVERING PLAY_SESSION COOKIE (BECAUSE NOT LOGGED IN).
             *
             * NOW, EXPECTED 'OK'
             */ 
            assertThat(status(result)).isEqualTo(OK); 
            assertThat(contentAsString(result)).contains(
                    "Something found");
        }
    });
}

The Application.list() is something like this:

@Security.Authenticated(Secured.class)
public static Result list() {
   return ok(list.render(...));
}


Maybe you are seeing https://bugs.launchpad.net/play/+bug/497408

I think it is supposed to work.

Update: I did some debugging. I think there are two bugs.

  1. In FunctionalTest, cookies are just discarded if they have no maxAge.
  2. In Scope.java, in Session, the "TS" field is only set if the cookie previously existed. Therefore, the first time the session cookie is set, it gets ignored when it's sent back to the server. If you make 3 requests, it seems to work OK between requests 2 and 3, but not between requests 1 and 2, because on request 2 the cookie sent doesn't have a timestamp.

So bug 1 breaks it if you don't set maxAge, and bug 2 breaks it if you do set maxAge.

Update 2: I made some patches that fix it for me: http://play.lighthouseapp.com/projects/57987-play-framework/tickets/775


Want to point out that there is an error in Mark S' final solution.

This is wrong: request.url = "/some/secured/action";

This is right: request.path = "/some/secured/action";

"url" should be a full url.

0

精彩评论

暂无评论...
验证码 换一张
取 消