Automated E2E testing of Alfresco through CAS

About this post

This post is about testing Alfresco webscripts when using CAS.

CAS is a great SSO solution, and there’s a number of reasons it’s a good fit for Alfresco: It’s open-source, web-based, written in Java, runs on essentially the same stack as Alfresco and provides graceful degradation into username/password login for those parts of your enterprise IT that don’t have nice SSL certificates stored in your user’s browsers. It’s also (compared to other SSO products) easy to install and manage. There’s a couple of headaches (the flash-based uploader provided by YUI and used in Share only works in IE with CAS enabled, for instance) but overall I’m very impressed with the ROI we’ve achieved by using CAS and Alfresco together.

As far as getting everything up and running is concerned, I’ve very little to say that isn’t covered better elsewhere. I’ve always used the mod_auth_cas route described on the Alfresco Wiki although Martin Bergljung has written about interfacing directly between CAS and Tomcat, if you prefer.

One thing that I did find a minor roadhump when developing webscripts with Alfresco was working out the best way to test my webscripts under CAS. Sure, you can deploy your webscripts to a non-CAS environment and test there, but A) this goes against the principal of making the test environment as similar as possible to production and B) sometimes I want to do final functional testing, or load/performance testing, on a production or near-production environment.

So here’s a couple of techniques I’ve been using to test webscripts under CAS. In all the below examples, I’ve had to assume that CAS is set up in a fairly standard manner, and that authentication is via username/password rather than by certificate.

Thanks to Gareth Ferrier from Surevine for working out how to connect via JMeter.

Chrome REST Console

There’s a plugin for Google Chrome called the REST Console. This provides a fairly straightforward UI for sending requests to REST providers (such as Alfresco) and viewing responses.

What’s nice here is that, because the console is within the browser, it operates within your existing Alfresco session. So all you have to do is log into CAS using the browser, then point the REST console at the target service and you’re done. There’s probably equivalent techniques for other browsers too. This is great for ad-hoc testing but it’s not practical to automate and you have to inspect the results for errors manually.

Shellscript / cURL

Now and again, I use the following script to instrument cURL in order to negotiate the CAS logon. I’ve tried to keep the script simple and small rather than flexible and high-quality. Depending on the exact infrastructure and effect I’m trying to achieve, I usually have to modify this script in various ways – when in doubt man curl. I’ll let the comments describe how the script works.

# Usage: alfrescoRestCall.sh {url} {username} {password} # If you have any errors try removing the redirects to get more information
# The service to be called, and a url-encoded version (the url encoding isn't perfect, if you're encoding complex stuff you may wish to replace with a different method)
DEST=$1
ENCODED_DEST=`echo $DEST | perl -p -e 's/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg' | sed 's/%2E/./g' | sed 's/%0A//g'`

#IP Addresses or hostnames are fine here
CAS_HOSTNAME=12.123.456.789

#Authentication details. This script only supports username/password login, but curl can handle certificate login if required
USERNAME=$2
PASSWORD=$3

#Temporary files used by curl to store cookies and http headers
COOKIE_JAR=.cookieJar
HEADER_DUMP_DEST=.headers
rm $COOKIE_JAR
rm $HEADER_DUMP_DEST

#The script itself is below

#Visit CAS and get a login form. This includes a unique ID for the form, which we will store in CAS_ID and attach to our form submission. jsessionid cookie will be set here
CAS_ID=`curl -k -c $COOKIE_JAR https://$CAS_HOSTNAME/cas/login?service=$ENCODED_DEST | grep name=.lt | sed 's/.*value..//' | sed 's/\".*//'`

#Submit the login form, using the cookies saved in the cookie jar and the form submission ID just extracted. We keep the headers from this request as the return value should be a 302 including a "ticket" param which we'll need in the next request
curl -k --data "username=$USERNAME&password=$PASSWORD<=$CAS_ID&_eventId=submit&submit=Sign%20In" -i -b $COOKIE_JAR -c $COOKIE_JAR https://$CAS_HOSTNAME/cas/login?service=$ENCODED_DEST > $HEADER_DUMP_DEST 2> /dev/null

#Linux may not need this line but my response from the previous call has retrieving windows-style linebreaks in OSX
dos2unix $HEADER_DUMP_DEST > /dev/null

#Visit the URL with the ticket param to finally set the casprivacy and, more importantly, MOD_AUTH_CAS cookie. Now we've got a MOD_AUTH_CAS cookie, anything we do in this session will pass straight through CAS
CURL_DEST=`cat < $HEADER_DUMP_DEST | grep Location | sed 's/Location: //'` curl -k -b $COOKIE_JAR -c $COOKIE_JAR $CURL_DEST > /dev/null 2>&1

#If our destination is not a GET we'll need to do a GET to, say, the user dashboard here

#Visit the place we actually wanted to go to
curl -k -b $COOKIE_JAR "$DEST" 2>/dev/null

This approach is easy to automate and can be called within a parent script to validate the results of your webscript calls, although of course it’s platform dependant and doesn’t integrate well into whichever general test framework you may be using. It’s main advantage is that it’s easy to take into customer environments as it doesn’t require the customer to download or install any software, and the script itself can even be retyped from a print-out easily enough, if required.

JMeter

I’m a big fan of JMeter for load testing and at the moment it’s doing a good job for me when automating the end-to-end testing of my REST applications.

It’s pretty easy to get JMeter set-up to login to CAS, call a bunch of webscripts and validate the results.  Unfortunately, because JMeter is a graphical program, it’s slightly harder to describe in a blog post. I’ll try and walk through the process step-by-step, which is probably more useful than just providing the JMeter XML that results.

In the below example I’ll assume that we want to test a new Share webscript, located at /mywebscripts/foobar, for functionality and performance. A call to the webscript is succesful if the response contaisn the String "status" : "success".

  1. Start with a new test plan, under the Test Plan add a Thread Group, and set the number of Threads to, say, 10
  2. Under the Thread Group, add a new Config Element->HTTP Cookie Manager. Set it to clear cookies each iteration and change the policy to
    compatability. Add a cookie named CASTGC with value ${castgc} and path /cas– tick the
    secure box. Add another cookie with name MOD_AUTH_CAS, value ${modauthcas} and path /share. This element ensures that JMeter will capture the various cookies used to negotiate with cas and store them in variables we can access later.
  3. Again under the Thread Group, create a new Config Element->HTTP Header Manager. Add a
    User-Agentvalue and set it to
    Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
  4. We need to set up some variables to hold the hostname of the server(s) to test and the username and password to login to CAS with. Under the Thread Group, create a new Config Element->User Defined Variables. Create variables called casHostnamealfrescoHostnameusername and password, and assign them appropriate values.
  5. Now we’ve set up the general behaviours of our JMeter test, we can start the test sequence itself. The first few parts of our test sequence will focus on negotiating a login with CAS. To start with, create a new Logic Controller->Simple Controller under the Thread Group and give it a sensible name – as our test set expands it will be handy to group sections of our test in this way. Underneath that, create a new Sampler->HTTP Request. Name it something like “Request Login Form”, set the hostname to ${casHostname}, the protocol to https(assuming that’s what you’re using), leave the method at GET and set the path to
    /cas/login?service=https%3a%2f%2f${alfrescoHostname}:443%2fshare%2f
  6. The previous step gets us the cas login form. This form sets a hidden field named “lt” that we need to extract to use alongside our response to the form. To extract this field, add a new Post Processor->Regular Expression Extractor to the Request Login Form element above. Set the reference name to “lt”, the regular expression to “<input type=”hidden” name=”lt” value=”([^”]+)”” and the template to $1$
  7. Now we’ve got an lt value to use, we can submit a login form to CAS. Create a new Sampler->HTTP Request underneath the Simple Controller set up in step 5. We still want to do a GET over https to the ${casHostname} server, and we still want to use the path “/cas/login?service=https%3a%2f%2f${alfrescoHostname}:443%2fshare%2f” but as we are submitting a login form we want to send a POST. We also need to send the following parameters with the request – it’s safest to encode every parameter:
    1. username: ${username}
    2. password: ${password}
    3. lt: ${lt}
    4. _eventId: submit
    5. submit: Sign In
  8. We now need to extract the CAS ticket and ticket-granting-ticket. To do this, we can use another Post Processor->Regular expression, this time added to the Sampler we created in the previous step. We want to extract information from the headers. I’ve set the reference to “modauthcas”, the regular expression to use to “Set-Cookie: MOD_AUTH_CAS=(.+)” and the template to “$1$”
  9. In theory, we’re now ready to go ahead and generate our request. However, and for reasons I can’t 100% remember right now, we need to make sure our first request to is a GET, as mod_auth_cas won’t set the mod_auth_cas ticket on a POST or PUT. I therefore always make my first request a GET to the user dashboard. This is a simple Sampler -> HTTP Request to /share/ over https
  10. We can now start our actual test. The test itself is a simple Sampler -> HTTP Request to, in this example, /mywebscripts/foobar over https. In order to check the results, we can use add an Assertion -> Response Assertion to the Sampler created in the previous step. In our example, where we just want to check that the response takes a certain value, we can simple Test the “Text Response” field, use the “Equals” pattern matching rule and enter "status" : "success" in the “Patterns to test” box. We can add extra tests or more complicated conditions here if we like

That’s all we need to do to set up a simple test in JMeter. You can run the test once to check that it works, but a lot of the value in JMeter comes from how easy it is to set up multiple threads running over several iterations and produce nice graphs and reports from these runs.

In Summary

Automating the testing of webscripts in Alfresco is an effective way of improving the quality of Alfresco customisations, particularly when sections of the a webscript are written in Javascript, Freemarker, or are otherwise difficult to fully JUnit.

CAS makes this a little but more difficult.

By building the CAS login logic into the automated test, and understanding which tickets and values to extract from CAS along the way, we can overcome this barrier without too much trouble.

Comments are closed.