Thursday, December 10, 2009

Coding around Shiro security in Grails integration tests

A number of our controllers work directly with the Shiro-authenticated user in a session, so when it comes to integration testing our controllers, we need a way of faking the Shiro actions. Groovy's metaprogramming techniques make it fairly easy.

There are three main pieces to be aware of in the Shiro authentication workflow: a Subject, the SecurityManager, and SecurityUtils. Creating a fake Subject is easy; the two things in Subject we need to worry about are the "principal" (more or less the user name) and whether or not that user is authenticated. We'll crate a Map with some simple closures and cast it as a Subject we can use:

import org.apache.shiro.subject.Subject

def subject = [ getPrincipal: { "iamauser" },
isAuthenticated: { true }
] as Subject

Now we can use that Subject wherever we need to return a subject for Shiro to act on.

The next step is making sure we have a SecurityManager in our Shiro ThreadContext object. With the SecurityManager, the only thing we need to worry about is that it returns our subject, so we'll again fake that with a Map and the "as" keyword:

import org.apache.shiro.util.ThreadContext

ThreadContext.put( ThreadContext.SECURITY_MANAGER_KEY,
[ getSubject: { subject } ] as SecurityManager )


Finally, since using Shiro's SecurityUtils makes it easy to deal with security objects in our controller code, we'll want to make sure it returns the values we want. In our case, all we really needed was the subject:

import org.apache.shiro.SecurityUtils

SecurityUtils.metaClass.static.getSubject = { subject }


That's it! In our controller test cases, we put that in our setUp() method, and we were then able to test our controllers that used Shiro's SecurityUtils to grab the current user like this:

def user
def subject = SecurityUtils.subject
if (subject.authenticated) {
user = ShiroUser.findByUsername( subject.principal )
} else {
// do something different
}


Putting together the pieces of the mock Shiro code looks like this:

import org.apache.shiro.subject.Subject
import org.apache.shiro.util.ThreadContext
import org.apache.shiro.SecurityUtils

def subject = [ getPrincipal: { "iamauser" },
isAuthenticated: { true }
] as Subject

ThreadContext.put( ThreadContext.SECURITY_MANAGER_KEY,
[ getSubject: { subject } ] as SecurityManager )

SecurityUtils.metaClass.static.getSubject = { subject }