Wednesday, April 28, 2010

Mock Testing with Grails

For unit testing controllers that make calls to injected spring beans, we can use Grails' mock framework.

Take for example a Controller and Service class defined as follows
class MyService {
boolean transactional = true

def serviceMethod(param1) {
return 'hi'
}
}

class MyDomainController {
def myService

def doSomething = {
def val = myService.serviceMethod('input1')
return val }
}

Our test case for the doSomething closure would look as follows
import grails.test.*

class MyDomainControllerTests extends ControllerUnitTestCase {

protected void setUp() {
super.setUp()
}

protected void tearDown() {
super.tearDown()
}

void testSomething() {
GrailsMock serviceMock = mockFor(MyService)

/*
* Mock the method being called
* the 1..1 specifies the min & max number of times the method is expected to be called
* the closure defines the code executed each time the method is called
*/

serviceMock.demand.serviceMethod(1..1) {param1 ->
assertEquals 'input1', param1
return 'hi'
}

/*
* The service needs to be manually set in the controller
*/

controller.myService = serviceMock.createMock()

assertEquals 'hi', controller.doSomething()

/*
* Verifies that the service was accessed
* in the manner established earlier.
*/

serviceMock.verify()
}
}

Wednesday, April 14, 2010

Bypassing Acegi Security

Acegi Security is rather easy to include in your application, but because our application needs to integrate with a single-sign on system, some method of bypassing Acegi or automatically logging into our application is required.

Since Acegi is built upon a list of filters that intercept HttpServletRequests, we can inject our own filter into the chain that will automatically log a user into our application. For simplicity, we assume the SSO is providing user information in a request header parameter.

The first step is include our filter in the list of filters to be invoked by Acegi. Edit conf\SecurityConfig.groovy, which should have been created during installation of the Acegi plugin, to include the following:
filterNames = ['httpSessionContextIntegrationFilter',
'logoutFilter',
'authenticationProcessingFilter',
'securityContextHolderAwareRequestFilter',
'rememberMeProcessingFilter',
'mySsoFilter',
'anonymousProcessingFilter',
'exceptionTranslationFilter',
'filterInvocationInterceptor']

The filters listed are the common filters invoked by Acegi in the listed order. We've included our filter mySsoFilter.


Since the filters in Acegi are actually Spring beans, the next step is to define the bean representing our filter in conf\spring\resources.groovy
mySsoFilter(MySsoFilter) {
userDetailsService = ref("userDetailsService")
}

The userDetailsService bean passed to our filter is pre-defined by Acegi and Grails. It's purpose is described later.


Finally we create our filter file, MySsoFilter.groovy, in the utils directory. The skeleton is listed below. Note
-- we defined an instance field userDetailsService, the Spring bean injected into our filter bean.
-- the method getOrder() defines the order and priority for the filter during invocation
-- the method doFilterHttp(...) is where the functionality of our filter will be contained.
import org.apache.commons.logging.LogFactory

import org.springframework.beans.factory.InitializingBean
import org.springframework.security.GrantedAuthority
import org.springframework.security.GrantedAuthorityImpl
import org.springframework.security.context.SecurityContextHolder
import org.springframework.security.providers.UsernamePasswordAuthenticationToken
import org.springframework.security.ui.FilterChainOrder
import org.springframework.security.ui.SpringSecurityFilter

import javax.servlet.FilterChain
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse


class MySsoFilter extends SpringSecurityFilter implements InitializingBean
{
static final HEADER_ATTRIBUTE_LOGIN = 'x-login'
static final HEADER_ATTRIBUTE_FIRSTNAME = 'x-firstname'
static final HEADER_ATTRIBUTE_LASTNAME = 'x-lastname'
static final HEADER_ATTRIBUTE_EMAIL = 'x-email'

static final log = LogFactory.getLog(MySsoFilter.class)

def userDetailsService

void afterPropertiesSet() {
log.debug '************************ inside afterPropertiesSet'
}


void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
chain.doFilter(request, response)
}

int getOrder() {
return FilterChainOrder.REMEMBER_ME_FILTER
}
}



Now that we have a filter being invoked in the proper order, containing information about the user who has been logged in via SSO, the difficulty now lies in simulating a login in Acegi.

First, we get information about the user we're trying to log in. This is provided by the userDetailsService bean, an instance of the Acegi implementation of the UserDetailsService. The interface's only method returns an instance of UserDetails, which is a wrapper around the loginUserDomainClass and authorityDomainClass defined during Acegi installation (and also defined in SecurityConfig.groovy).
def details = userDetailsService.loadUserByUsername(username) 


Next, we create a security token, by using the concrete class UsernamePasswordAuthenticationToken. The password is obtained during lookup of the user.
def token = new UsernamePasswordAuthenticationToken(
details, passwd, details.authorities)


Finally, we give the token to the SecurityContextHolder, an Acegi object which manages the security token per executing thread.
SecurityContextHolder.context.authentication = token 



Our final implementation for doFilterHttp(...) is as follows
    void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
log.debug '************************ inside doFilterHttp'
log.debug 'req value ' + request.getHeader(HEADER_ATTRIBUTE_LOGIN)

// no SSO header, do not continue
def login = request.getHeader(HEADER_ATTRIBUTE_LOGIN)
if (!login) {
log.debug('no sso header found, continuing')
chain.doFilter(request, response)
return
}

// user not in our system, do not continue
def user = User.findByUsername(login)
if (!user) {
log.info('username ' + login + ' not found')
chain.doFilter(request, response)
return
}

// user does not have admin role, do not continue
def adminRole = Role.findByAuthority('ROLE_ADMINISTRATOR')
if (!user.authorities.contains(adminRole)) {
log.info('user ' + login + ' does not have admin permission')
chain.doFilter(request, response)
return
}

log.info('user ' + login + ' given SSO access')
def details = userDetailsService.loadUserByUsername(user.username)
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
details, user.passwd, details.authorities)
SecurityContextHolder.context.authentication = token
chain.doFilter(request, response)
}

Tuesday, April 13, 2010

GrailsUI Tab Manipulation

We've been using the Tab Layout available in the GrailsUI plugin in our application, but one minor annoyance we've discovered is that while saving the form resets the active tab.

The javascript library that renders the tabs gives you access to the tab information, through the variable GRAILSUI

For example, the following gsp would always display the first tab on save.

<gui:tabView id="myTabs">
<gui:tab label="Basic" active="true">
</gui:tab>
<gui:tab label="Firms">
</gui:tab>
</gui:tabView>



We can now access the tabs in custom javascript by using
GRAILSUI.myTabs.get('activeIndex')


all that's left is to save a hidden field, and change the tab 'active' definitions

Custom Grails Error Handling

Error handling in your Grails app is especially important, as Groovy is a non-checked language -- methods need not declare a throws clause. While UrlMappings.groovy provides a default page for general 500 errors, in our application, we'd like to display different error pages in different situations.

There are several approaches to this problem. One solution is to separate error handling by exception class. This can be done by modifying Bootstrap.groovy

exceptionHandler is an instance of GrailsExceptionResolver, and can be auto injected. Note the last entry is needed for default handling.

Also, be sure to remove the "500" entry from UrlMappings.groovy, as interferes with the definitions here.

class BootStrap {
def exceptionHandler

def init = {
exceptionHandler.exceptionMappings = [
'SpecialException' : '/myController/someAction',
'java.lang.Exception' : '/error'
]
}
}



Another approach is to modify the handling in UrlMappings.groovy. By default, the "500" entry points to error.gsp in the views directory, but it can be changed to another view, or a controller with optional action specified.
"500"(controller:'myController', action:'handleExceptions')