Rest Assured and CAS Login forms

1,226 views
Skip to first unread message

Stephen Rufle

unread,
Dec 19, 2013, 4:28:44 PM12/19/13
to rest-a...@googlegroups.com
I am trying to write a test for a RESTful service behind a CAS Login screen. I have tried to do in the following code what I think is the equivalent action in a browser.
  1. GET the login form return as content in the first response
  2. Pick the "lt" value which I am assuming it a CAS token of some sort
  3. Pick the session id so I can make a new call using the same session
  4. POST as if I was the login form
  5. This is where I get the login form again, I expected a 302 redirect that I could get values from the header so I had a valid CAS ticket.
Java method
    @Test
    public void restExample() throws Exception {

        String testUserName = "testUser";
        String testPassword = "abc123";

// @formatter:off
         Response response = given().redirects().follow(true)
                              .param("service","https://qa.example.com/webops/rs/users/testUser")
                              .get("https://qa.example.com/cas/login").andReturn();
// @formatter:on
        String loginHtml = response.asString();
        String sessionId = response.getCookie("JSESSIONID");

        // Cas Form gets returned, parse values so we can do the POST
        Document loginDoc = Jsoup.parse(loginHtml);
        String ltTagValue = loginDoc.select("input[name=lt]").val();
        String executionTagValue = loginDoc.select("input[name=execution]").val();
// @formatter:off
        response = given().spec(givenSpec).redirects().follow(true)
                    .cookie("JSESSIONID", sessionId)
                    .param("service","https://qa.example.com/webops/rs/users/testUser")
                    .formParam("lt", ltTagValue)
                    .formParam("execution", executionTagValue)
                    .formParam("username", testUserName)
                    .formParam("password", URLEncoder.encode(testPassword, "UTF-8"))
                    .formParam("_eventId", "submit")
                    .formParam("submit", "LOGIN")
                    .post(
"https://qa.example.com/cas/login");
// @formatter:on

        // Should be JSON from service, but is the form again
        System.out.println(response.asString());

    }
   

   
- Get request for login page in FF, logged using LiveHeaders plugin

https://qa.example.com/cas/login?service=https://qa.example.com/webops/rs/users/testUser

GET /cas/login?service=https://qa.example.com/webops/rs/users/testUser HTTP/1.1
Host: qa.example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: JSESSIONID=05C7F147538CF8BF78211320B9BA1F47.primary; __utma=51866153.1870478125.1373305118.1385161014.1386793161.17; __utmz=51866153.1384887347.14.2.utmcsr=issues.flpi.com|utmccn=(referral)|utmcmd=referral|utmcct=/browse/SUPPORT-522; acopendivids=tab1,tab2,tab3,tab4,jason; acgroupswithpersist=tabs
Connection: keep-alive

HTTP/1.1 200 OK
Date: Thu, 19 Dec 2013 17:43:01 GMT
Server: Apache/2.2.3 (CentOS)
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store
Content-Length: 3893
Connection: close
Content-Type: text/html;charset=UTF-8
----------------------------------------------------------

- Posting login page in FF, logged using LiveHeaders plugin
https://qa.example.com/cas/login?service=https://qa.example.com/webops/rs/users/testUser

POST /cas/login?service=https://qa.example.com/webops/rs/users/testUser HTTP/1.1
Host: qa.example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://qa.example.com/cas/login?service=https://qa.example.com/webops/rs/users/testUser
Cookie: JSESSIONID=3414683706A44A923295C646616D45F8.primary; __utma=51866153.1870478125.1373305118.1385161014.1386793161.17; __utmz=51866153.1384887347.14.2.utmcsr=issues.flpi.com|utmccn=(referral)|utmcmd=referral|utmcct=/browse/SUPPORT-522; acopendivids=tab1,tab2,tab3,tab4,jason; acgroupswithpersist=tabs
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 122
username=testUser&password=abc123&lt=LT-968-lvdXiAC4kH3QcgIH1ecVlpEWLaDjry&execution=e1s1&_eventId=submit&submit=LOGIN
HTTP/1.1 302 Found
Date: Thu, 19 Dec 2013 18:03:27 GMT
Server: Apache/2.2.3 (CentOS)
Pragma: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store
Set-Cookie: CASPRIVACY=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/cas/
Set-Cookie: CASTGC=TGT-180-Zz1srKpRh6e2BFcD2pGcRMw7ce2QoTcNLruyLdojQHxSi7bLic-flpi.com; Path=/cas/; Secure
Location: https://qa.example.com/webops/rs/users/testUser?ticket=ST-547-uBKXquokIIIcCkYmaXIH-flpi.com
Content-Length: 0
Connection: close
Content-Type: text/plain; charset=UTF-8
----------------------------------------------------------
https://qa.example.com/webops/rs/users/testUser?ticket=ST-547-uBKXquokIIIcCkYmaXIH-flpi.com

GET /webops/rs/users/testUser?ticket=ST-547-uBKXquokIIIcCkYmaXIH-flpi.com HTTP/1.1
Host: qa.example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://qa.example.com/cas/login?service=https://qa.example.com/webops/rs/users/testUser
Cookie: JSESSIONID=-X3F2TocMW-eIdYBF2RFGVkN;
Connection: keep-alive

HTTP/1.1 302 Moved Temporarily
Date: Thu, 19 Dec 2013 18:03:27 GMT
Server: Apache/2.2.3 (CentOS)
Set-Cookie: JSESSIONID=NnYgquFLK3kQiYLjhjhKf8kz; Path=/webops; Secure
Location: https://qa.example.com/webops/rs/users/testUser
Content-Length: 0
Connection: close
Content-Type: text/plain; charset=UTF-8
----------------------------------------------------------
https://qa.example.com/webops/rs/users/testUser

GET /webops/rs/users/testUser HTTP/1.1
Host: qa.example.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/26.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://qa.example.com/cas/login?service=https://qa.example.com/webops/rs/users/testUser
Cookie: JSESSIONID=NnYgquFLK3kQiYLjhjhKf8kz;
Connection: keep-alive


----------------------------------------------------------

Stephen Rufle

unread,
Dec 24, 2013, 11:37:10 AM12/24/13
to rest-a...@googlegroups.com
I have upgraded to RA 2.1.0 in my pom. I am able to get the process to work if I use org.apache.commons.httpclient.HttpClient to make all the GET's and POST's. The important part is that when you POST the CAS login form you get a Location header with the ticket value on the 302 redirect that you then use to re request the original GET. 

This article seems to say if you post and get back a 302 there is no way RestAssured will return you the 302 response so I can make a subsequent GET 

If I am wrong and there is a way, I would love to find out :)

Johan Haleby

unread,
Jan 6, 2014, 5:02:11 AM1/6/14
to rest-a...@googlegroups.com
Sorry for the late response but perhaps you could just use form authentication? E.g.

given().auth().form(testUserName, testUserPassword) (if needed supply a FormAuthConfig as third parameter). 

I don't remember CAS all that well since it was more than 8 years ago I last used it.


Regards,
/Johan


--
You received this message because you are subscribed to the Google Groups "REST assured" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rest-assured...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Stephen Rufle

unread,
Jan 6, 2014, 12:28:13 PM1/6/14
to rest-a...@googlegroups.com
I did as you suggested and I still get the form back after my get

        @Test
public void restExample() throws Exception {

String casUserName = "testUser";
String casUserPassword = "abc123";
FormAuthConfig fac = new FormAuthConfig(formAction, "username", "password");
Response response = given().auth().form(casUserName, casUserPassword, fac).get("https://qa.example.com/webops/rs/users/testUser").andReturn();

// Should be JSON from service, but is the form again
System.out.println(response.asString());
}

I was able to work around the issue by using raw org.apache.commons.httpclient.* method calls. I suspect that the reason it does not work with teh form auth is that you need to do something special (ex. passing more values) to satisfy CAS. 

    public String get(String url) {
        String answer = null;
        InputStream inputStream = null;
        PostMethod casPostMethod = null;
        GetMethod originalServiceGetMethod = new GetMethod(url);

        try {
            // Execute the method.
            int statusCode = client.executeMethod(originalServiceGetMethod);
            if (statusCode == HttpStatus.SC_OK) {

                // Read the response body.
                inputStream = originalServiceGetMethod.getResponseBodyAsStream();
                String possibleLoginHtml = IOUtils.toString(inputStream, "UTF-8");
                originalServiceGetMethod.releaseConnection();

                // On Login page
                if (StringUtils.trimToEmpty(possibleLoginHtml).indexOf("name=\"lt\"") > -1) {

                    // Find session cookie
                    Cookie[] cookies = client.getState().getCookies();
                    Cookie sessionCookie = null;
                    for (int i = 0; i < cookies.length; i++) {
                        String name = cookies[i].getName();
                        if ("JSESSIONID".equalsIgnoreCase(name)) {
                            sessionCookie = cookies[i];
                            break;
                        }
                    }
                    // Find form values
                    Document loginDoc = Jsoup.parse(possibleLoginHtml);
// This is an important value that needs to be passed back to the CAS server
                    String ltTagValue = loginDoc.select("input[name=lt]").val();
                    String executionTagValue = loginDoc.select("input[name=execution]").val();
                    // Post the CAS info
                    String casServiceUrl = uriBuilder.setPath(casUrl).addParameter("service", url).build().toString();

                    casPostMethod = new PostMethod(casServiceUrl);
                    casPostMethod.setFollowRedirects(false);

                    // Prepare login parameters
                    NameValuePair lt = new NameValuePair("lt", ltTagValue);
                    NameValuePair execution = new NameValuePair("execution", executionTagValue);
                    NameValuePair userid = new NameValuePair("username", getCasUser());
                    NameValuePair password = new NameValuePair("password", getCasPassword());
                    NameValuePair eventId = new NameValuePair("_eventId", "submit");
                    NameValuePair submit = new NameValuePair("submit", "LOGIN");
                    casPostMethod.setRequestBody(new NameValuePair[] { lt, execution, userid, password, eventId, submit });
                    casPostMethod.setRequestHeader(new Header("Cookie", sessionCookie.toExternalForm()));
                    client.executeMethod(casPostMethod);
                    casPostMethod.releaseConnection();

                    int statuscode = casPostMethod.getStatusCode();
                    if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) || (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) || (statuscode == HttpStatus.SC_SEE_OTHER) || (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) {
                        Header locationHeader = casPostMethod.getResponseHeader("location");
                        if (locationHeader != null) {
                            String serviceRedirectUri = locationHeader.getValue();
                            if ((serviceRedirectUri == null) || (serviceRedirectUri.equals(""))) {
                                serviceRedirectUri = "/";
                            }

                            GetMethod serviceRedirectGetMethod = new GetMethod(serviceRedirectUri);
                            client.executeMethod(serviceRedirectGetMethod);
                            inputStream = serviceRedirectGetMethod.getResponseBodyAsStream();
                            String jsonResponse = IOUtils.toString(inputStream, "UTF-8");
                            answer = jsonResponse;
                            serviceRedirectGetMethod.releaseConnection();

                        }
                    }
                } else {
                    // Should be the the service response
                    // Read the response body.
                    answer = possibleLoginHtml;
                }
            }
        } catch (HttpException e) {
            logger.error("Fatal protocol violation: ", e);
        } catch (IOException e) {
            logger.error("Fatal transport error: ", e);
        } catch (URISyntaxException e) {
            logger.error("Syntax error in URL", e);
        }

        return answer;
    }


Stephen P Rufle
stephen...@cox.net
H1:480-269-JAVA (5282)

Johan Haleby

unread,
Jan 7, 2014, 3:25:42 AM1/7/14
to rest-a...@googlegroups.com
Would form authentication perhaps work if we would allow specifying additional headers to the FormAuthConfig? Also what version of Rest Assured are you using?

If this works it would be simple to add CAS authentication as authentication scheme in REST Assured as well.

Regards,
/Johan

Stephen Rufle

unread,
Jan 7, 2014, 11:48:33 AM1/7/14
to rest-a...@googlegroups.com
I did think that CAS could be a variant of form authentication. Its possible if FormAuthConfig had knowledge of  the "lt" hidden field and there was some way to get its value from the returned form that it could work. I see that when you do form authentication that you have the username and password passed in to the form() method. If you created a .cas() method I am not sure how you can interrogate the returned form. I had assumed when doing form based auth that you only act like a form and POST what is expected. 

I will try to dummy in what I think might be a way. (NOTE: I did try to checkout the source from Git, but maven did not create a proper Eclipse project when I did mvn eclipse:eclipse and is complaining about org.codehaus.gmaven:gmaven-plugin:1.5. I tried looking for how all the chained methods work, but got lost :) )

// If "lt" is a standard name then there would be no need to pass it
CasAuthConfig cac = new CasAuthConfig(
casAction , "username", "password" ,"lt");

// In .cas() I assume you would have to do the proper "dance" and either return the response or an error
Response response = given().auth().cas(casUserName, casUserPassword, cac).get(restAction).andReturn();

I am using this in my pom.xml
        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>2.1.0</version>
        </dependency>

Stephen P Rufle
stephen...@cox.net
H1:480-269-JAVA (5282)



Al Krinker

unread,
Apr 3, 2014, 6:28:34 PM4/3/14
to rest-a...@googlegroups.com
Here is an example that I wrote of how to link Rest and CAS
 
 
Hope it helps

Johan Haleby

unread,
Apr 4, 2014, 2:13:41 AM4/4/14
to rest-a...@googlegroups.com
Thanks for sharing.


--
You received this message because you are subscribed to the Google Groups "REST assured" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rest-assured...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages