Casifying Luntbuild

England I’ve been working a lot lately in devware which is a vmware virtual machine containing a lot of development tools already installed and integrated. The challenge now is to support Single Sign On, so that only one login is necessary to access all the tools and softwares inside (at least the ones that has a web interface).

It happens that devware is composed of heterogeneous 3rd party tools, and obviously each one uses a different kind of authentication and security schema.

That’s where comes to scene CAS (Central Authentication Service) a very neat solution for single sign on for the web.

Luntbuild, which is a great and full featured build automation server is the first software that was “Casified”. Luntbuild, fortunately, was created using Spring and Acegi, which in turn supports CAS integration. So, this is how I Casified luntbuild:

1. Install and make sure that CAS is working

Install CAS is as hard as drop a WAR inside your favourite web container. The not-so-obvious part are the SSL issues: you and everyone must access cas using a name, and not and IP, and SSL is obligatory. And you must have a certificate (can be self signed) that reflects this name. After that, make sure that you can login in CAS accessing https://<server>/cas/login.

2. Make sure luntbuild is installed and running

The version of luntbuild that I casified was 1.5.5. This process will not work on previous versions, such as 1.3.x ones. Make sure that luntbuild is installed and that it’s possible to do a login using it’s own security scheme.

3. Changing luntbuild’s web.xml

No change is needed! 🙂 luntbuild’s web.xml declares a filterToBean proxy that allows to make all filtering stuff inside spring’s application context.

4. Changing applicationContext.xml

This is where luntbuild’s spring appcontext lies.

Locate the bean “authenticationManager” and add to the list (as the first one) the CAS authentication provider:

<bean id="authenticationManager" class="com.luntsys.luntbuild.security.AuthenticationProviderManager">
                <property name="providers">
                        <list>
                                <ref local="casAuthenticationProvider" />

                                <!-- authentication provider which uses declarative security -->
                                <ref local="inMemoryAuthenticationProvider" />

                                <!-- authentication provider for remember me -->
                                <ref local="rememberMeAuthenticationProvider" />

                                <!-- authentication provider which validates users again internal db -->
                                <ref local="luntbuildAuthenticationProvider" />

                                <!-- authentication provider which validates users again Ldap -->
                                <ref local="ldapAuthenticationProvider" />
                        </list>
                </property>
        </bean>

Search and coment the bean id “exceptionTranslationFilter”:

<!--<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
             <property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
</bean> -->

Locate the bean “filterChainProxy” and change the URL matching rules to:

<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
     <property name="filterInvocationDefinitionSource">
        <value>
            PATTERN_TYPE_APACHE_ANT
            /*.do=httpSessionContextIntegrationFilter,casProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
            /j_acegi_cas_security_check=httpSessionContextIntegrationFilter,casProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
            /casProxy/receptor=httpSessionContextIntegrationFilter,casProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
       </value>
    </property>
</bean>

And then add the beans of the CASAuthenticationProvider:

<bean id="casAuthenticationProvider" class="org.acegisecurity.providers.cas.CasAuthenticationProvider">
               <property name="casAuthoritiesPopulator">
                        <ref bean="casAuthoritiesPopulator" />
                </property>
                <property name="casProxyDecider">
                        <ref bean="casProxyDecider" />
                </property>
                <property name="ticketValidator">
                        <ref bean="casProxyTicketValidator" />
                </property>
                <property name="statelessTicketCache">
                        <ref bean="statelessTicketCache" />
                </property>
                <property name="key">
                        <value>my_password_for_this_auth_provider_only</value>
                </property>
</bean>

<bean id="casProxyDecider" class="org.acegisecurity.providers.cas.proxy.RejectProxyTickets" />

<bean id="statelessTicketCache" class="org.acegisecurity.providers.cas.cache.EhCacheBasedTicketCache">
                <property name="cache">
                        <ref local="ticketCacheBackend" />
                </property>
</bean>

<bean id="ticketCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
                <property name="cacheManager">
                        <ref local="cacheManager" />
                </property>
                <property name="cacheName">
                        <value>ticketCache</value>
                </property>
</bean>
   <bean id="casProxyTicketValidator" class="org.acegisecurity.providers.cas.ticketvalidator.CasProxyTicketValidator">
                <property name="casValidate">
                        <value>https://devware/cas/proxyValidate</value>
                </property>
                <property name="proxyCallbackUrl">
                        <value>https://devware/luntbuild/casProxy/receptor</value>
                </property>
                <property name="serviceProperties">
                        <ref bean="serviceProperties" />
                </property>
</bean>

<bean id="casAuthoritiesPopulator" class="com.luntsys.luntbuild.security.LuntbuildCasAuthoritiesPopulator">
                <property name="authenticationDao">
                        <ref bean="luntbuildAuthenticationDAO" />
                </property>
</bean>

<bean id="serviceProperties" class="org.acegisecurity.ui.cas.ServiceProperties">
                <property name="service">
                        <value>https://devware/luntbuild/j_acegi_cas_security_check</value>
                </property>
                <property name="sendRenew">
                        <value>false</value>
                </property>
</bean>

<bean id="casProcessingFilter" class="org.acegisecurity.ui.cas.CasProcessingFilter">
                <property name="authenticationManager">
                        <ref bean="authenticationManager" />
                </property>
                <property name="authenticationFailureUrl">
                        <value>/casfailed.jsp</value>
                </property>
                <property name="defaultTargetUrl">
                        <value>/</value>
                </property>
                <property name="filterProcessesUrl">
                        <value>/j_acegi_cas_security_check</value>
                </property>
</bean>

<bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
                <property name="authenticationEntryPoint">
                        <ref local="casProcessingFilterEntryPoint" />
                </property>
</bean>

<bean id="casProcessingFilterEntryPoint" class="org.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
                <property name="loginUrl">
                        <value>https://devware/cas/login</value>
                </property>
                <property name="serviceProperties">
                        <ref bean="serviceProperties" />
                </property>
</bean>

Replace “https://devware&#8221; with your cas host.

The final piece of the puzzle is the casAuthoritiesPopulator declared above. This is necessary to populate the luntbuild user’s credentials accordingly after the user has logged on, and to create the user in the luntbuild database it does not exist already, similar to what happens when integration luntbuild with LDAP. I’ve submitted a patch to luntbuild’s tracker with this class

5. Final remarks

After this procedure, if one logins into cas, and after that enters luntbuild, a second login will not be necessary. If someone tries to enter luntbuild directly, it’ll redirect to CAS login page. And the same happens with every other applications that is “Casified”. Without changing the source code of luntbuild at all (just changing XML and adding one more class), it was possible to change a major application behavior, and that’s the beauty of spring based projects. They are so decoupled that nearly every aspect of an application instantly becomes an extension point!

And the use of CAS allows to have SSO and not depend on things like proprietary realms, intrusive server configurations, and obscure plugins that are target to a certain version of a certain application server maker. CAS is very portable, the only requisite is an application container that support servlets.


Advertisements