Monday, April 16, 2012

Grails boostrap method injection

When getting the domain class name use the following:

String className = domainClass.clazz.name

Monday, April 9, 2012

iframe resizing

Problem:

The problem is getting the iframe to automatically resize based on the content of the child iframe.

The majority of the solutions to this problem found on the web, expect that both the parent and the child iframe belong to the same domain, and thus avoid the "cross domain" security restriction when accessing the childIFrame.body.scrollHeight  property.



Solution:

When I found this post
he gave me the idea of posting event messages between the parent and child iframe.

I decided to use the jQuery plugin found here for posting messages

Basically, it works as follows. The child iframe posts a message to the parent with its scroll height upon loading the page. The parent listens for messages and when it is received, it adjusts the height of the iframe according to the scroll height it receives from the child iframe. See the example code below:

Parent:
<script type="text/javascript">

    $(document).ready(function() {

        $.receiveMessage( 
                function(e) {
                    alert(e.data);
                    var h = Number( e.data.replace( /.*if_height=(\d+)(?:&|$)/, '$1' ) );
                    //alert('h='+h);
                    if ( !isNaN( h ) && h > 0 ) {                        
                        $('#snvFrame').height(h);                        
                    }
                    
                },                
                /*
                function(e) {
                    //alert(e);
                    return true;
                }*/
            );
         
     });


</script>

<iframe id="snvFrame" src="<%= PhenomeConfig.getInstance().getIncidentalSNPsUrl() %>" width="100%" frameborder="0" height="100%" allowtransparency="true">


</iframe>


Child:
        <script type="text/javascript">

             $(document).ready(function() {                
                $.postMessage({ if_height: $(document).height() }, 'http://localhost:8989/phenbank/incidentalSNPs.html');                
             });

        </script>




References:

Monday, April 2, 2012

Grails Domain class reflection on properties

If you want to determine what the persistent fields are for a domain class at runtime using reflection, try the following:

         DefaultGrailsDomainClass dgdc = new DefaultGrailsDomainClass(clazz)
         logger.info("properties="+dgdc.getPersistentProperties())

Tuesday, March 20, 2012

Grails instanceOf vs Java instanceof

In Grails, there is a method attached to each domain object called "instanceOf" click here

It is important to note that this method is different from the Java instanceof operator.

The Grails version of instanceOf, is used to test instances that have been wrapped as hibernate proxies for their true object type.
These proxies only subclass the generic type used to identify it's type. The problem of dealing with Hibernate proxies has been explained in more detail in this other blog entry of mine here.

Wednesday, February 29, 2012

Grails security with CAS and LDAP

Problem:

I have multiple web applications that share the same users. The problem is my users should not have to login or authenticate multiple times to access each of the web applications. The solution was to use single sign on (SSO) using a combination of Central Authentication Service (CAS) and LDAP,  from the spring security plugins for Grails.

Solution:

Unfortunately, I wasn't able to use the CAS plugin alone, because my requirements needed to access the user's email address which was stored in LDAP.
Here, I've used CAS for authentication purposes with single-sign-on support and I've used LDAP to retrieve user details such as email address and display names, and my local database to determine the roles for each user.


The process of authentcation occurs with the following steps:
  1. CAS Authentcation
  2. Retrieve user details from LDAP
  3. Save user details to the database if it doesn't already exist 
Step 3 is required, because of the way the CAS plugin works. In the default behaviour, the CAS plugin expects that the User domain objects to already exist in the local database. Hence, even if authentication is successful at the CAS server, it will fail at your Grails application when it attempts to load the user details from the database. To workaround this, I've created a customized UserDetailsService implementation as outlined by the offical docs here. In this implementation, we are saving users to the database on demand. This avoids us from having to prepopulate users in our local database and is more dynamic.
The code is shown below:

package apf.bioinformatics.security

import org.apache.log4j.Logger
import org.codehaus.groovy.grails.plugins.springsecurity.GormUserDetailsService
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.ldap.userdetails.InetOrgPerson
import org.springframework.security.ldap.userdetails.LdapUserDetailsService;

import apf.bioinformatics.Role
import apf.bioinformatics.UserRole
import apf.bioinformatics.enums.RoleType


/**
 * Prepopulates the database with the user details from LDAP directory and assigns a default Role to the user
 * @author Philip Wu
 *
 */
class PrepopulateUserDetailsService extends GormUserDetailsService {

    Logger logger = Logger.getLogger(getClass())
    
    LdapUserDetailsService ldapUserDetailsService
        
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {                
        return loadUserByUsername(username, true)
    }

    @Override
    public UserDetails loadUserByUsername(String username, boolean loadRoles)
            throws UsernameNotFoundException, DataAccessException {
                
                
        UserDetails userDetails = ldapUserDetailsService.loadUserByUsername(username)
        
        if (userDetails instanceof InetOrgPerson) {
            
            InetOrgPerson inetOrgPerson = (InetOrgPerson) userDetails
            logger.info("mail="+inetOrgPerson.getMail())
            
            apf.bioinformatics.User user = apf.bioinformatics.User.findByUsername(username)
            if (user == null) {
                
                apf.bioinformatics.User.withTransaction {
                
                    // Create new user and save to the database
                    user = new apf.bioinformatics.User()
                    user.username = username
                    user.email = inetOrgPerson.getMail()
                    user.displayName = inetOrgPerson.getDisplayName()
                    user.enabled = true
                    user.save()
        
                    Role clientRole = Role.findByAuthority(RoleType.INVESTIGATOR.toString())
                                
                    // Assign the default role of client
                    UserRole userRole = new UserRole()
                    userRole.user = user
                    userRole.role = clientRole
                    userRole.save()
                    logger.info("user saved to database")
                }
            }
                                
        }
        
        logger.info("ldap user details: "+userDetails)
        
        // Load user details from database
        return super.loadUserByUsername(username, loadRoles)                                
    }

}


The important part of the above code is in creating the user if it doesn't already exist prior to attempting to load from the database. This means the user is always available as long as the CAS server successfully passed authentication.

You may notice that there is an instance of LdapUserDetailsService. This is used to retrieve the email address and display name of the user directly from LDAP, which is pretty straight forward as shown in the above code. The tricky part is loading the configuration and wiring up the classes required for the LdapUserDetailsService in the resources.groovy file and Config.groovy as shown below:

resources.groovy
    def config = SpringSecurityUtils.securityConfig
    SpringSecurityUtils.loadSecondaryConfig 'DefaultLdapSecurityConfig'
    config = SpringSecurityUtils.securityConfig

    initialDirContextFactory(org.springframework.security.ldap.DefaultSpringSecurityContextSource,
       config.ldap.context.server){
        userDn = config.ldap.context.managerDn
        password = config.ldap.context.managerPassword        
        anonymousReadOnly = config.ldap.context.anonymousReadOnly
    }

    ldapUserSearch(org.springframework.security.ldap.search.FilterBasedLdapUserSearch, 
       config.ldap.search.base,
       config.ldap.search.filter,       
        initialDirContextFactory){
    }

    ldapAuthoritiesPopulator(org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator,
        initialDirContextFactory,
       config.ldap.authorities.groupSearchBase){
          groupRoleAttribute = config.ldap.authorities.groupRoleAttribute
          groupSearchFilter = config.ldap.authorities.groupSearchFilter
          searchSubtree = config.ldap.authorities.searchSubtree
          convertToUpperCase = config.ldap.mapper.convertToUpperCase
          ignorePartialResultException = config.ldap.authorities.ignorePartialResultException
    }

    ldapUserDetailsMapper(InetOrgPersonContextMapper)
       
    ldapUserDetailsService(org.springframework.security.ldap.userdetails.LdapUserDetailsService,
        ldapUserSearch,
        ldapAuthoritiesPopulator){        
        userDetailsMapper = ref('ldapUserDetailsMapper')        
    }    
    
    userDetailsService(PrepopulateUserDetailsService) {
        ldapUserDetailsService=ref('ldapUserDetailsService')
        grailsApplication = ref('grailsApplication')
    }



Config.groovy
// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'apf.bioinformatics.User'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'apf.bioinformatics.UserRole'
grails.plugins.springsecurity.authority.className = 'apf.bioinformatics.Role'


// Define the authentication providers
grails.plugins.springsecurity.providerNames = ['casAuthenticationProvider']


// Define the CAS configuration
grails.plugins.springsecurity.cas.loginUri = '/login'
grails.plugins.springsecurity.cas.serviceUrl =   '${grails.serverURL}/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.serverUrlPrefix = 'https://login-test.anu.edu.au' //'https://your-cas-server/cas'
grails.plugins.springsecurity.cas.proxyCallbackUrl = '${grails.serverURL}/secure/receptor' //'${grails.serverURL}'
grails.plugins.springsecurity.cas.proxyReceptorUrl = '/secure/receptor'
grails.plugins.springsecurity.cas.key = 'grailsCasTest'


// Define the LDAP configuration
grails.plugins.springsecurity.ldap.context.server = 'ldap://ldap.anu.edu.au:389'
grails.plugins.springsecurity.ldap.authorities.groupSearchBase ='ou=People,o=anu.edu.au'
grails.plugins.springsecurity.ldap.search.base ='ou=People,o=anu.edu.au'
grails.plugins.springsecurity.ldap.search.attributesToReturn = ['uid','mail', 'displayName']
grails.plugins.springsecurity.ldap.mapper.userDetailsClass= 'inetOrgPerson'// 'org.springframework.security.ldap.userdetails.InetOrgPerson'
grails.plugins.springsecurity.ldap.mapper.usePassword= false
grails.plugins.springsecurity.ldap.authenticator.dnPatterns='uid={0},ou=People,o=anu.edu.au'
grails.plugins.springsecurity.ldap.context.anonymousReadOnly=true
grails.plugins.springsecurity.ldap.authorities.ignorePartialResultException = true
grails.plugins.springsecurity.ldap.authorities.retrieveDatabaseRoles = true



It's important to note that the configuration for the LdapUserDetailsService is configured from Config.groovy

I hope this helps another Grails developer out there as it took me a day and half to figure this out, with some help provided by the online references listed below.

References:

Tuesday, February 21, 2012

Grails: Fails to save from within afterInsert closure

When trying to save a new domain object from within an afterInsert event, an exception was thrown using the following code:

    def afterInsert = {
        ADomainClass domainObject = new ADomainClass()
        domainObject.save() // <------------- THIS LINE FAILS
    }

When I modified the code slightly, then it worked as follows:


    def afterInsert = {
        ADomainClass.withNewSession {
           ADomainClass domainObject = new ADomainClass()
           domainObject.save() //
        }
    }

Reference: http://jira.grails.org/browse/GRAILS-4851

Monday, February 6, 2012

STS grails Unsupported major.minor version 51.0

I recently upgraded to JDK version 7 on my development machine, which started to cause an error in my development IDE, Spring's STS for Grails development

I configured the workspace settings to use the newly installed JDK 7 as shown below:



 When I went to run the application, I got the following error:

Error Error packaging application: Error occurred processing message bundles: java.lang.UnsupportedClassVersionError: sun/tools/native2ascii/Main : Unsupported major.minor version 51.0

After some researching on the web, it turns out the Java version used to COMPILE the code was different from the java version used to RUN the code.

It turns out, my run configuration (right click project -> run as -> run configurations) was set to use the older Java version 6 as shown below:



I switched it to use Java 7, and now the application runs again!




Reference:
 http://stackoverflow.com/questions/8398956/grails-deployment-error