Wednesday, February 7, 2007

Roller v3.0 with Active Directory (non-SSL)

Roller v3.0 on Weblogic Platform v8.1.5 with Active Directory


I added integration with Active Directory after getting Roller to run on WebLogic. However, the process is fairly complicated and there is as yet no SSL. But if you are also looking into doing the same thing perhaps the below steps will help to shorten the learning curve. Enjoy!

Preparation
1) Amend '{DefaultApp}\roller\WEB-INF\security.xml'.
    1.1) Remove the line 'anonymousProcessingFilter' as below.

    <!-- ======================== FILTER CHAIN ======================= -->
    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
               CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
               PATTERN_TYPE_APACHE_ANT
               /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,rememberMeProcessingFilter,
               channelProcessingFilter,remoteUserFilter,exceptionTranslationFilter,filterInvocationInterceptor
            </value>
        </property>
    </bean>

    1.2) Comment out 'daoAuthenticationProvider' and 'anonymousAuthenticationProvider' and add 'ldapAuthProvider' as below.

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
               <ref local="ldapAuthProvider"/>
               <!--<ref local="daoAuthenticationProvider"/>-->
               <!--<ref local="anonymousAuthenticationProvider"/>-->
               <!-- rememberMeAuthenticationProvider added programmatically -->
            </list>
        </property>
    </bean>

    1.3) Uncomment and modify the LDAP parameters to suit your
environment. A sample is given as below.
    <!-- Sample LDAP/RollerDB hybrid security configuration -->
    <bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
        <constructor-arg value="ldap://192.168.0.100:389/DC=roller,DC=com"/>
            <property name="managerDn">
               <value>CN=tommy,OU=Users,DC=roller,DC=com</value>
            </property>
            <property name="managerPassword">
               <value>xxxxx</value>
            </property>
            <property name="useConnectionPool">
               <value>false</value>
            </property>
    </bean>

    <bean id="ldapUserSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
        <constructor-arg index="0">
           <value></value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>sAMAccountName={0}</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref local="initialDirContextFactory"/>
        </constructor-arg>
        <property name="searchSubtree">
           <value>true</value>
        </property>
    </bean>

    <bean id="ldapAuthProvider"
            class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
               <constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
               <property name="userSearch"><ref bean="ldapUserSearch"/></property>
            </bean>
        </constructor-arg>
        <constructor-arg><ref local="jdbcAuthoritiesPopulator"/></constructor-arg>
        <property name="userCache" ref="userCache"/>
    </bean>

    <bean id="jdbcAuthoritiesPopulator" class="org.apache.roller.ui.core.security.AuthoritiesPopulator">
        <property name="dataSource">
            <bean class="org.springframework.jndi.JndiObjectFactoryBean">
               <property name="jndiName" value="jdbc/rollerdb"/>
            </bean>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>SELECT username,rolename FROM userrole WHERE username = ?</value>
        </property>
        <property name="defaultRole"><value>register</value></property>
    </bean>

    1.4) Comment out the below to prevent anonymous users.
    <!--<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
            <property name="key" value="anonymous"/>
    </bean>-->

    <!--
    <bean id="anonymousProcessingFilter" class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
        <property name="key" value="anonymous"/>
        <property name="userAttribute" value="anonymous,ROLE_ANONYMOUS"/>
    </bean>
    -->

2) As we are no longer allowing users to register anonymously, we need to enable auto-provisioning. Add the custom properties file, '{DefaultApp}\roller\WEB-INF\classes\roller-custom.properties' with the following properties (Note: the property 'users.sso.enabled' must be set so that the CustomUserRegistry class will return the authenticated LDAP user data).
    # Enables Roller to behave differently when registering new users
    # in an SSO-enabled environment. You must configure security.xml appropriately.
    users.sso.enabled=true

    # Set these properties for a custom LDAP schema (optional).
    # Note by Damon: Change below parameters accordingly for your environment
    users.sso.registry.ldap.attributes.name=displayName
    users.sso.registry.ldap.attributes.email=userPrincipalName

    users.sso.autoProvision.enabled=true
    users.sso.autoProvision.className=org.apache.roller.ui.core.security.BasicUserAutoProvision

3) After configuring the above, you should get user auto-provisioning working. Just go to the login page (e.g. http://localhost:7001/roller/roller-ui/login.do). Enter the AD user id and password to login.

    However, the default logic now will result in a '403 - Access Denied' HTTP error immediately after the new user login (although the new user account will be created in the DB table by now). The '403' error arise will disappear once the user cache in the application's memory is cleared (such as when application restart). In order for a new user to proceed to create his weblog he needs at least a 'editor' role. Currently, the roles are added when the ACEGI security filter classes retrieve any additional privileges from a DB table while creating the user principal object. For new accounts, this happens too late as the HibernateUserManagerImpl class only adds the additional 'editor' role into the DB table after the user principal object was created. There are probably a few ways to tackle this. I chose to extend the ACEGI classes LdapAuthenticationProvider and create a custom class in '{temp}\roller\src\org\apache\roller\ui\providers\RollerLdapAuthenticationProvider.java'. This class will check if a user exists after a successful LDAP retrieval. If the user does not exists, it will insert a default "editor" role for the new user.

    In addition, we need to extend RollerSession class so that it will not only provision a new user account in the DB table but also re-populate the user principal with the newly granted roles. The re-population will require recreating the user principal object, reset the Authentication object in the SecurityContextHolder and refresh the cache in the ACEGI class AbstractUserDetailsAuthenticationProvider. To refresh the cache, we will also have to extend AbstractUserDetailsAuthenticationProvider as well as a few other ACEGI classes.

4) Add 'RollerLdapAuthenticationProvider.java' into '{temp}\roller\src\org\apache\roller\ui\providers\ldap\',
code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers.ldap;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.AuthenticationServiceException;

import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.ldap.LdapAuthenticator;
import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator;
import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider;

import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.dao.DataAccessException;
// For user provision and reauthentication, Damon Chong.
import org.apache.roller.model.RollerFactory;
import org.apache.roller.model.UserManager;
import org.apache.roller.pojos.UserData;
import org.apache.roller.config.RollerConfig;
import
org.apache.roller.ui.providers.dao.RollerAbstractUserDetailsAuthenticationProvider;
import org.apache.roller.ui.providers.RollerUsernamePasswordAuthenticationToken;
import org.apache.roller.ui.providers.ldap.authenticator.RollerBindAuthenticator;
import org.acegisecurity.GrantedAuthorityImpl;
import org.apache.roller.RollerException;

public class RollerLdapAuthenticationProvider extends RollerAbstractUserDetailsAuthenticationProvider {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(RollerLdapAuthenticationProvider.class);

    //~ Instance fields ================================================================================================

    private LdapAuthenticator authenticator;
    private LdapAuthoritiesPopulator authoritiesPopulator;

    //~ Constructors ===================================================================================================

    public RollerLdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
        Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
        Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");

        this.authenticator = authenticator;
        this.authoritiesPopulator = authoritiesPopulator;
    }

    //~ Methods ========================================================================================================

    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                                   UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (!userDetails.getPassword().equals(authentication.getCredentials().toString())) {
            throw new BadCredentialsException(messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);
        }
    }

    protected UserDetails createUserDetails(LdapUserDetails ldapUser, String username, String password) {
        LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
        user.setUsername(username);
        user.setPassword(password);

        GrantedAuthority[] extraAuthorities = authoritiesPopulator.getGrantedAuthorities(ldapUser);

        for (int i = 0; i < extraAuthorities.length; i++) {
           user.addAuthority(extraAuthorities[i]);
        }

        return user.createUserDetails();
    }

    protected UserDetails createNewUserDetails(LdapUserDetails ldapUser, String username, String password) {
        LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
        user.setUsername(username);
        user.setPassword(password);

        GrantedAuthority[] extraAuthorities = authoritiesPopulator.getGrantedAuthorities(ldapUser);

        for (int i = 0; i < extraAuthorities.length; i++) {
           user.addAuthority(extraAuthorities[i]);
        }
        // For new user we grant them an additional role so that they don't have a 403 error.
        GrantedAuthority editorAuth = new GrantedAuthorityImpl("editor");
        user.addAuthority(editorAuth);

        return user.createUserDetails();
    }

    protected LdapAuthoritiesPopulator getAuthoritiesPoulator() {
        return authoritiesPopulator;
    }

    protected UserDetails retrieveUserAgain(String username, RollerUsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (!StringUtils.hasLength(username)) {
            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
               "Empty Username"));
        }

        String password = (String) authentication.getCredentials();

        if (logger.isDebugEnabled()) {
           logger.debug("Retrieving user " + username);
        }

        try {
           LdapUserDetails ldapUser;

            // As password will be null or empty, we cannot authenticate with an invalid password
            // so we extended BindAuthenticator to accept only one parameter
            ldapUser = ((RollerBindAuthenticator)authenticator).authenticate(username);

            return createUserDetails(ldapUser, username, password);

        } catch (DataAccessException ldapAccessFailure) {
            throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
        }
    }

    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (!StringUtils.hasLength(username)) {
            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
               "Empty Username"));
        }

        if (logger.isDebugEnabled()) {
           logger.debug("Retrieving user " + username);
        }

        String password = (String) authentication.getCredentials();
        Assert.notNull(password, "Null password was supplied in authentication token");

        if (password.length() == 0) {
           logger.debug("Rejecting empty password for user " + username);
            throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
               "Empty Password"));
        }

        try {
           LdapUserDetails ldapUser = authenticator.authenticate(username, password);
            // Always check if the user exists.
            if( isNewUser(username) )
            {
               return createNewUserDetails(ldapUser, username, password);
            }
            else{
               return createUserDetails(ldapUser, username, password);
            }

        } catch (DataAccessException ldapAccessFailure) {
            throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
        }
    }

    private boolean isNewUser(String username){

        try
        {
            UserManager umgr = RollerFactory.getRoller().getUserManager();
            UserData user = umgr.getUserByUserName(username);

            if(user == null && RollerConfig.getBooleanProperty("users.sso.autoProvision.enabled")) {
               return true;
            }
        }
        catch (RollerException e)
        {
           e.printStackTrace();
        }

        return false;
    }
}

5) Add 'RollerBindAuthenticator.java' into into '{temp}\roller\src\org\apache\roller\ui\providers\ldap\dao\',
code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers.ldap.authenticator;

import org.acegisecurity.BadCredentialsException;

import org.acegisecurity.providers.ldap.authenticator.AbstractLdapAuthenticator;
import org.acegisecurity.ldap.InitialDirContextFactory;
import org.acegisecurity.ldap.LdapTemplate;

import org.acegisecurity.userdetails.ldap.LdapUserDetails;
import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Iterator;

public class RollerBindAuthenticator extends AbstractLdapAuthenticator {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(RollerBindAuthenticator.class);

    //~ Constructors ===================================================================================================

    public RollerBindAuthenticator(InitialDirContextFactory initialDirContextFactory) {
        super(initialDirContextFactory);
    }

    //~ Methods ========================================================================================================

    public LdapUserDetails authenticate(String username, String password) {
        LdapUserDetails user = null;

        // If DN patterns are configured, try authenticating with them directly
        Iterator dns = getUserDns(username).iterator();

        while (dns.hasNext() && (user == null)) {
            user = bindWithDn((String) dns.next(), username, password);
        }

        // Otherwise use the configured locator to find the user
        // and authenticate with the returned DN.
        if ((user == null) && (getUserSearch() != null)) {
           LdapUserDetails userFromSearch = getUserSearch().searchForUser(username);
            user = bindWithDn(userFromSearch.getDn(), username, password);
        }

        if (user == null) {
            throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
        }

        return user;
    }

    public LdapUserDetails authenticate(String username) {
        LdapUserDetails user = null;

        // If DN patterns are configured, try authenticating with them directly
        Iterator dns = getUserDns(username).iterator();

        while (dns.hasNext() && (user == null)) {
            user = bindWithDn((String) dns.next(), username);
        }
       
        // Otherwise use the configured locator to find the user
        // and authenticate with the returned DN.
        if ((user == null) && (getUserSearch() != null)) {
           LdapUserDetails userFromSearch = getUserSearch().searchForUser(username);           

            user = bindWithDn(userFromSearch.getDn(), username);           

        }

        if (user == null) {
            throw new BadCredentialsException(messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
        }

        return user;
    }

    private LdapUserDetails bindWithDn(String userDn, String username, String password) {
        LdapTemplate template = new LdapTemplate(getInitialDirContextFactory(), userDn, password);

        if (logger.isDebugEnabled()) {
           logger.debug("Attempting to bind with DN = " + userDn);
        }

        try {
           LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence)template.retrieveEntry(userDn,
               getUserDetailsMapper(), getUserAttributes());
           user.setUsername(username);

            return user.createUserDetails();
        } catch (BadCredentialsException e) {
            // This will be thrown if an invalid user name is used and the method may
            // be called multiple times to try different names, so we trap the exception.
            if (logger.isDebugEnabled()) {
               logger.debug("Failed to bind as " + userDn + ": " + e.getCause());
            }
        }

        return null;
    }

    private LdapUserDetails bindWithDn(String userDn, String username) {
        LdapTemplate template = new LdapTemplate(getInitialDirContextFactory());

        if (logger.isDebugEnabled()) {
           logger.debug("Attempting to bind with DN = " + userDn);
        }

        try {
           LdapUserDetailsImpl.Essence user = (LdapUserDetailsImpl.Essence) template.retrieveEntry(userDn,
           getUserDetailsMapper(), getUserAttributes());
           user.setUsername(username);

            return user.createUserDetails();
        } catch (BadCredentialsException e) {
            // This will be thrown if an invalid user name is used and the method may
            // be called multiple times to try different names, so we trap the exception.
            if (logger.isDebugEnabled()) {
               logger.debug("Failed to bind as " + userDn + ": " + e.getCause());
            }
        }

        return null;
    }
}

6) Add 'RollerUsernamePasswordAuthenticationToken.java' into into '{temp}\roller\src\org\apache\roller\ui\providers\', code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers;

import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;


public class RollerUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
    //~ Constructors ===================================================================================================
    public RollerUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public RollerUsernamePasswordAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities) {
        super(principal, credentials, authorities);
    }

}

7) Add 'RollerAbstractUserDetailsAuthenticationProvider.java' into into '{temp}\roller\src\org\apache\roller\ui\providers\dao', code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.providers.dao;

import org.acegisecurity.AccountExpiredException;
import org.acegisecurity.AcegiMessageSource;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.BadCredentialsException;
import org.acegisecurity.CredentialsExpiredException;
import org.acegisecurity.DisabledException;
import org.acegisecurity.LockedException;

import org.acegisecurity.providers.AuthenticationProvider;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.dao.cache.NullUserCache;
import org.acegisecurity.providers.dao.UserCache;

import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;

import org.springframework.beans.factory.InitializingBean;

import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;

import org.springframework.util.Assert;
// For re-authentication, Damon Chong.
import org.apache.roller.ui.providers.RollerUsernamePasswordAuthenticationToken;

public abstract class RollerAbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
    MessageSourceAware {
    //~ Instance fields ================================================================================================

    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;

    //~ Methods ========================================================================================================

    protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException;
   
    public final void afterPropertiesSet() throws Exception {
        Assert.notNull(this.userCache, "A user cache must be set");
        Assert.notNull(this.messages, "A message source must be set");
        doAfterPropertiesSet();
    }

    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
       Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
           messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
               "Only UsernamePasswordAuthenticationToken is supported"));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

        boolean cacheWasUsed = true;
        boolean reAuthenticate = (authentication instanceof RollerUsernamePasswordAuthenticationToken);
   
        UserDetails user = this.userCache.getUserFromCache(username);

        if ( (user == null) || reAuthenticate ){

            cacheWasUsed = false;

            try {
               if( reAuthenticate ){
                   user = retrieveUserAgain(username, (RollerUsernamePasswordAuthenticationToken) authentication);
               }
               else{
                   user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
               }

            } catch (UsernameNotFoundException notFound) {
               notFound.printStackTrace();
               if (hideUserNotFoundExceptions) {
                   throw new BadCredentialsException(messages.getMessage(
                       "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
               } else {
                   throw notFound;
               }
            } catch (Exception e) {
               e.printStackTrace();
            }

           Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }
   
        if (!user.isAccountNonLocked()) {
            throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
               "User account is locked"));
        }

        if (!user.isEnabled()) {
            throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
               "User is disabled"));
        }

        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
               "User account has expired"));
        }

        // This check must come here, as we don't want to tell users
        // about account status unless they presented the correct credentials
        try {
           additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);   
        } catch (AuthenticationException exception) {
        // There was a problem, so try again after checking we're using latest data
            cacheWasUsed = false;
            user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
            additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)
authentication);
        }

        if (!user.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException(messages.getMessage(
               "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
        }

        if (!cacheWasUsed) {
           this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
           principalToReturn = user.getUsername();
        }
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
    UserDetails user) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
                                                                           authentication.getCredentials(), user.getAuthorities());
        result.setDetails(authentication.getDetails());

        return result;
    }

    protected void doAfterPropertiesSet() throws Exception {}

    public UserCache getUserCache() {
        return userCache;
    }

    public boolean isForcePrincipalAsString() {
        return forcePrincipalAsString;
    }

    public boolean isHideUserNotFoundExceptions() {
        return hideUserNotFoundExceptions;
    }

    protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

    protected abstract UserDetails retrieveUserAgain(String username, RollerUsernamePasswordAuthenticationToken authentication)
    throws AuthenticationException;

    public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
        this.forcePrincipalAsString = forcePrincipalAsString;
    }
   
    public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
        this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    public void setUserCache(UserCache userCache) {
        this.userCache = userCache;
    }

    public boolean supports(Class authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

8) Add 'RollerAuthenticationProcessingFilter.java' into into '{temp}\roller\src\org\apache\roller\ui\webapp',
code as below:

/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.roller.ui.webapp;

import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.context.SecurityContextHolder;

import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;

import org.acegisecurity.ui.AbstractProcessingFilter;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.roller.ui.providers.RollerUsernamePasswordAuthenticationToken;

public class RollerAuthenticationProcessingFilter extends AbstractProcessingFilter {
    //~ Static fields/initializers =====================================================================================

    public static final String ACEGI_SECURITY_FORM_USERNAME_KEY = "j_username";
    public static final String ACEGI_SECURITY_FORM_PASSWORD_KEY = "j_password";
    public static final String ACEGI_SECURITY_LAST_USERNAME_KEY = "ACEGI_SECURITY_LAST_USERNAME";

    //~ Methods ========================================================================================================

    public Authentication attemptAuthentication(HttpServletRequest request)
    throws AuthenticationException {
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        UsernamePasswordAuthenticationToken authRequest;

        // Damon Chong, enhanced to recreate credentials after auto provisioning.
        String usrname = null;
        if( null==(usrname=(String)request.getAttribute(
            RollerAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY)) ){
            authRequest = new UsernamePasswordAuthenticationToken(username, password);
        }
        else{
            // This is a re-authentication after user provision was completed. Damon.
            username = usrname; // as username will be null.
            authRequest = new RollerUsernamePasswordAuthenticationToken(username, password);
        }

        // Place the last username attempted into HttpSession for views
        request.getSession().setAttribute(ACEGI_SECURITY_LAST_USERNAME_KEY, username);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        Authentication authResult = this.getAuthenticationManager().authenticate(authRequest);
        // Reset the principal, Damon. 9/1/07.
        if( null!=usrname ){
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }

        return authResult;
    }

    public String getDefaultFilterProcessesUrl() {
        return "/j_acegi_security_check";
    }

    public void init(FilterConfig filterConfig) throws ServletException {}

    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(ACEGI_SECURITY_FORM_PASSWORD_KEY);
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(ACEGI_SECURITY_FORM_USERNAME_KEY);
    }

    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
}

9) Modify '{temp}\roller\src\org\apache\roller\ui\core\RollerSession.java' as below:

(Import the additional classes)

// Added by Damon Chong to re-authenticate again. 5/1/2007
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.apache.roller.ui.webapp.RollerAuthenticationProcessingFilter;

(Insert the following line of codes after the line 'user = umgr.getUserByUserName(principal.getName());')

// One more thing to do if user is provision,
// call authentication again to get any additional,
// granted authorities. Damon, 5/1/2007.
    ApplicationContext ctx =
       WebApplicationContextUtils.getRequiredWebApplicationContext(
    request.getSession().getServletContext());
    RollerAuthenticationProcessingFilter filter =
        (RollerAuthenticationProcessingFilter) ctx.getBean("authenticationProcessingFilter");
   
// Re-authenticate again to populate any additional
// grants. Flag for RollerAuthenticationProcessingFilter
// to treat as an re-authentication due to provisioning
// and for RollerLdapAuthenticationProvider not to
// failed it due to an empty length password.
    request.setAttribute(
           RollerAuthenticationProcessingFilter.ACEGI_SECURITY_FORM_USERNAME_KEY,
           principal.getName());

    filter.attemptAuthentication(request);

10) Modify '{DefaultApp}\roller\WEB-INF\security.xml' to make use of new
classes.

    <bean id="ldapAuthProvider"
            class="org.apache.roller.ui.providers.ldap.RollerLdapAuthenticationProvider">
        <constructor-arg>
            <bean class="org.apache.roller.ui.providers.ldap.authenticator.RollerBindAuthenticator">

    <bean id="authenticationProcessingFilter" class="org.apache.roller.ui.webapp.RollerAuthenticationProcessingFilter">