Friday 2 August 2013

Grails Tip: Different URL depending on user's role

"We need to redirect users depending on their roles". OMG how I was supposed to do this? Well after doing some search on the usual site, I found this:

http://omarello.com/2011/09/grails-custom-target-urls-after-login/

My solution is based on the previous blog entry. The only thing I've added is using Config.groovy to register different ROLE/URL cases. I thought it worthed sharing it.

Let's say we want to redirect users with role ROLE_MANAGER  to /users/search and users with role ROLE_PROVIDER to /invoices/search. So I came up with the following lines in my Config.groovy

authenticationurl{
 mappings{
  ROLE_MANAGER.controller='users'
  ROLE_MANAGER.action='search'
  ROLE_PROVIDER.controller='invoices'
  ROLE_PROVIDER.action='search'
 }
}


The nice thing about configuration files in Grails is that you can build maps just sharing the same root, in other words, I can ask which controller and action should be used for a manager accessing to the key ROLE_MANAGER:

def map = authenticationurl.mappings.ROLE_MANAGER
def controller = map.controller // 'users'
def action = map.action // 'search'


Taking into account that premise, I built my own AuthenticationHandler. This handler takes the authenticated user's role and use it as key to look for its correspondent controller/action:

import org.codehaus.groovy.grails.plugins.springsecurity.AjaxAwareAuthenticationSuccessHandler
import org.springframework.security.core.Authentication

import org.apache.commons.logging.LogFactory

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

import static org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils.ifAllGranted


class AuthenticationHandler extends AjaxAwareAuthenticationSuccessHandler {

 static log = LogFactory.getLog(AuthenticationHandler)

 def grailsLinkGenerator 
 def configurationService
 def springSecurityService

 @Override
 protected String determineTargetUrl(HttpServletRequest request,HttpServletResponse response) {
  /* Taking configuration mappings */ 
  def authMappings = configurationService.authenticationHandlerUrlMappings
  def defaultUrl = super.determineTargetUrl(request, response)
  if(log.isDebugEnabled()){
   log.debug("Default URL: $defaultUrl")
  }
  /* Taking the first match or the targetUrl  */
  return authMappings.findResult{k,v->
   if(ifAllGranted(k)){
    /* Here's better to get it absolute */
    grailsLinkGenerator.link(v << [absolute:true])
   }
  } ?: defaultUrl 
 }

}


The line with the authMapping variable assignment is doing grailsApplication.config. authenticationurl.mappings underneath. The value is itself a map of maps.

The method determineTargetUrl loops through the map looking for the first result matching the user's role. If there's an entry the user will be redirected to the configured URL otherwise the default URL will be used (normally it is the same URL where the user was when he tried to authenticate himself).

Notice that I'm using the service grailsLinkGenerator to generate the proper link passing the controller and action arguments to it. This works the same as if we were using the tag in any gsp. This service is available by default in Grails.

Ah of course don't forget to register your authentication handler in the resources.groovy file

authenticationSuccessHandler(AuthenticationHandler) {
        /* Reusing the security configuration */
        def conf = SpringSecurityUtils.securityConfig
        /* Configuring the bean */
        requestCache = ref('requestCache')
        redirectStrategy = ref('redirectStrategy')
        springSecurityService = ref('springSecurityService')
  grailsLinkGenerator = ref('grailsLinkGenerator')
  configurationService = ref('configurationService')
        defaultTargetUrl = conf.successHandler.defaultTargetUrl
        alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
        targetUrlParameter = conf.successHandler.targetUrlParameter
        ajaxSuccessUrl = conf.successHandler.ajaxSuccessUrl
        useReferer = conf.successHandler.useReferer
    }

No comments:

Post a Comment