cancel
Showing results for 
Search instead for 
Did you mean: 

CAS Authentication problem with Alfresco 2

alarocca
Champ in-the-making
Champ in-the-making
Hi,

as I wrote few weeks ago, I successfully had Alfresco 1.4 authenticating user by means of CAS (see here).

Now setting the same configuration with Alfresco 2, I get the following error:

org.alfresco.error.AlfrescoRuntimeException: Transaction must be active and synchronization is required
 

Any hint?

The following is the CASAuthenticationFilter (based on NovellIChainsHTTPRequestAuthenticationFilter) I use:

package org.alfresco.web.app.servlet;

import java.io.IOException;
import java.util.List;
import java.util.Locale;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.transaction.UserTransaction;

import org.alfresco.config.ConfigService;
import org.alfresco.i18n.I18NUtil;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.LoginBean;
import org.alfresco.web.bean.repository.User;
import org.alfresco.web.config.LanguagesConfigElement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import edu.yale.its.tp.cas.client.filter.CASFilter;

public class CASAuthenticationFilter extends AbstractAuthenticationFilter implements Filter
{
    private static final String LOCALE = "locale";

    public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient";

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

    private ServletContext context;

    private String loginPage;

    private AuthenticationComponent authComponent;

    private AuthenticationService authService;

    private TransactionService transactionService;

    private PersonService personService;

    private NodeService nodeService;

    private List<String> m_languages;

    public CASAuthenticationFilter()
    {
        super();
    }

    public void destroy()
    {
        // Nothing to do
    }

    public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException,
            ServletException
    {
        // Get the HTTP request/response/session

        HttpServletRequest req = (HttpServletRequest) sreq;
        HttpServletResponse resp = (HttpServletResponse) sresp;

        HttpSession httpSess = req.getSession(true);

        // Check for the CAS header
       
        String authHdr = (String) req.getSession().getAttribute(CASFilter.CAS_FILTER_USER);
       
       
        if(logger.isDebugEnabled())
        {
            if(authHdr == null)
            {
                logger.debug(CASFilter.CAS_FILTER_USER + " header not found.");
            }
            else
            {
                logger.debug(CASFilter.CAS_FILTER_USER + " header is <" + authHdr + ">");
            }
        }

        // Throw an error if we have an unknown authentication

        if ((authHdr == null) || (authHdr.length() < 1))
        {
            resp.sendRedirect(req.getContextPath() + "/jsp/noaccess.jsp");
            return;
        }

        // Get the user

        String userName = authHdr;

        if(logger.isDebugEnabled())
        {
            logger.debug("User = "+ userName);
        }
       
        // See if there is a user in the session and test if it matches

        User user = (User) httpSess.getAttribute(AuthenticationHelper.AUTHENTICATION_USER);

        if (user != null)
        {
            try
            {
                // Debug

                if (logger.isDebugEnabled())
                    logger.debug("User " + user.getUserName() + " validate ticket");

                // Validate the user ticket

                if (user.getUserName().equals(userName))
                {
                    // Set the current locale
                    authComponent.setCurrentUser(user.getUserName());
                    I18NUtil.setLocale(Application.getLanguage(httpSess));
                    chain.doFilter(sreq, sresp);
                    return;
                }
                else
                {
                    // No match
                    setAuthenticatedUser(req, httpSess, userName);
                }
            }
            catch (AuthenticationException ex)
            {
                if (logger.isErrorEnabled())
                    logger.error("Failed to validate user " + user.getUserName(), ex);
            }
        }

        setAuthenticatedUser(req, httpSess, userName);

        // Redirect the login page as it is never seen as we always login by name
        if (req.getRequestURI().endsWith(getLoginPage()) == true)
        {
            if (logger.isDebugEnabled())
                logger.debug("Login page requested, chaining …");

            resp.sendRedirect(req.getContextPath() + "/faces/jsp/browse/browse.jsp");
            return;
        }
        else
        {
            chain.doFilter(sreq, sresp);
            return;
        }
    }

    private void setAuthenticatedUser(HttpServletRequest req, HttpSession httpSess, String userName)
    {
        // Set the authentication
        authComponent.setCurrentUser(userName);

      

        // Set up the user information
        UserTransaction tx = transactionService.getUserTransaction();
        NodeRef homeSpaceRef = null;
        User user;
        try
        {
            tx.begin();
            user = new User(userName, authService.getCurrentTicket(), personService.getPerson(userName));
            homeSpaceRef = (NodeRef) nodeService.getProperty(personService.getPerson(userName),
                    ContentModel.PROP_HOMEFOLDER);
            user.setHomeSpaceId(homeSpaceRef.getId());
            tx.commit();
        }
        catch (Throwable ex)
        {
            logger.error(ex);

            try
            {
                tx.rollback();
            }
            catch (Exception ex2)
            {
                logger.error("Failed to rollback transaction", ex2);
            }
           
            if(ex instanceof RuntimeException)
            {
                throw (RuntimeException)ex;
            }
            else
            {
                throw new RuntimeException("Failed to set authenticated user", ex);
            }
        }

        // Store the user

        httpSess.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user);
        httpSess.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE);

        // Set the current locale from the Accept-Lanaguage header if available

        Locale userLocale = parseAcceptLanguageHeader(req, m_languages);

        if (userLocale != null)
        {
            httpSess.setAttribute(LOCALE, userLocale);
            httpSess.removeAttribute(MESSAGE_BUNDLE);
        }

        // Set the locale using the session

        I18NUtil.setLocale(Application.getLanguage(httpSess));
    }

    public void init(FilterConfig config) throws ServletException
    {
        this.context = config.getServletContext();
        WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
        ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
        transactionService = serviceRegistry.getTransactionService();
        nodeService = serviceRegistry.getNodeService();

        authComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
        authService = (AuthenticationService) ctx.getBean("authenticationService");
        personService = (PersonService) ctx.getBean("personService");

        // Get a list of the available locales

        ConfigService configServiceService = (ConfigService) ctx.getBean("webClientConfigService");
        LanguagesConfigElement configElement = (LanguagesConfigElement) configServiceService.
              getConfig("Languages").getConfigElement(LanguagesConfigElement.CONFIG_ELEMENT_ID);

        m_languages = configElement.getLanguages();
    }

    private String getLoginPage()
    {
        if (loginPage == null)
        {
            loginPage = Application.getLoginPage(context);
        }

        return loginPage;
    }

}
6 REPLIES 6

sebastien_marin
Champ in-the-making
Champ in-the-making
Hi, so i  am trying to do the same thing as you and i have the same starck trace.  I apply the cas Filter for 1.4 (post…) in a liferay 4.2.1 and alfresco 2.0 community.

So that problem is registered in :
http://issues.alfresco.com/browse/AR-1263
The topic talking about it is :
http://forums.alfresco.com/viewtopic.php?t=5125&postdays=0&postorder=asc&start=30


So if i understand, the bug is as been patched in the Alfresco 2.0 Enterprise version and will be in the next Community Release.

So can Alfresco Team send us the modification that they apply in the NTLM auth… class. We will probably able to apply them in teh CAS Auth.. class


So, good luck, we've got the same aim.

Thank to the Alfresco team and community.

andy
Champ on-the-rise
Champ on-the-rise
Hi

In 2.0 the "authenticationComponent" and "authenticationService" had some historical transaction support removed.

You can fix the issue by calling these service as you have them inside a user transaction. Alternatively, you can just use the transactional versions of the services, i.e. "AuthenticationComponent" and "AuthenticationService".

For example:

authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent");

The NTLM filter was modified to use the public services, as above (those starting with a capital letter)

Regards

Andy

sebastien_marin
Champ in-the-making
Champ in-the-making
Hi, i try to use the services with the capiotal letter and it is OK, i have no synchronisation error.

Now, the ticket is validated, the current user is set, it is put on the httpsession, ……
All is ok, i am redirected to the browse.jsp and there is no error but the problem is that i am on the guest welcome page and not connected with my current user…


What could be the problem ? What are the parameter to set to be redirected with the current user .

Thank you, we will soon plug-in the CAS Autologin !

sebastien_marin
Champ in-the-making
Champ in-the-making
ok, so i have news. My cas filter works fine when i acces to the webapp with /alfresco context.


So my problem is that my Alfresco is in a Liferay portal. The AlfrescoFacesPortlet is called before CASFilter.

There is no user the first time and no viewID so a GUEST user is authentificated in the PortletSession.

In my CAS Filter, i work on the HTTPSession.

So, alfresco portlet seems to look for the portlet Session user and not the httpSession user.

Do you have any idea to solve that problem ?


Thank you very much.

sebastien_marin
Champ in-the-making
Champ in-the-making
hi, thank you to remove the bad post lol.

So, i have no idea how i can apply the CAS Filter or NTLM filter or Novell Filter in a portal environment…


Can you help me .?

ananius
Champ in-the-making
Champ in-the-making
Filters don't get called in portlet environment.

With Liferay 4.2.1 + Alfresco 1.4 we have CAS-authentication working, based partly on posts here, eg http://forums.alfresco.com/viewtopic.php?t=3014 . I'm not sure if this helps with Alfresco 2.0, but I hope so.

So we have done following changes:

1. AlfrescoFacesPortlet -class (user has already logged in via CASFilter configured in liferay's web.xml)

at first line of processAction and facesRender we call ensureLogin(request), and the ensureLogin-method is implemented followingly:


    private void ensureLogin(PortletRequest request) {


        javax.portlet.PortletContext portletContext = getPortletContext();
        WebApplicationContext ctx = (WebApplicationContext)portletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
        NodeService fNodeService = serviceRegistry.getNodeService();
        TransactionService fTransactionService = serviceRegistry.getTransactionService();

        AuthenticationService fAuthService = (AuthenticationService) ctx.getBean("authenticationService");
        AuthenticationComponent fAuthComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
        PersonService fPersonService = (PersonService) ctx.getBean("personService");
        AuthenticationDao authenticationDao = (AuthenticationDao) ctx.getBean("authenticationDao");

        PortletSession session = request.getPortletSession();

        User user = (User) session.getAttribute(AuthenticationHelper.AUTHENTICATION_USER);

        if (user == null) {
            if (logger.isDebugEnabled()) {
                logger.debug("session user attirbute is null.");
            }

//            String ssoUser = Utils.getSsoUser(session);
            String ssoUser = request.getRemoteUser();

            UserTransaction tx = fTransactionService.getUserTransaction();
            NodeRef homeSpaceRef = null;

            try {
                tx.begin();

                fAuthComponent.setCurrentUser(""+ssoUser);

                // The user name used may be a different case to the NTLM supplied user name, read the current
                // user and use that name
                String userName = fAuthComponent.getCurrentUserName();

                // Setup User object and Home space ID etc.
                NodeRef personNodeRef = fPersonService.getPerson(userName);
                String currentTicket = fAuthService.getCurrentTicket();
                user = new User(userName, currentTicket, personNodeRef);

                homeSpaceRef = (NodeRef) fNodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
                user.setHomeSpaceId(homeSpaceRef.getId());

                tx.commit();
            }
            catch (Throwable ex) {
                try {
                    tx.rollback();
                }
                catch (Exception ex2) {
                    logger.error("Failed to rollback transaction", ex2);
                }
                ex.printStackTrace(); // todo: temp
            }

            // Store the user

            session.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user);

            // I'm not sure if this parameter is necessary?
            session.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE);

            // Note! If you wanted to do any fancy Locale stuff, you should do it here.

            if (logger.isDebugEnabled()) {
                logger.debug("User logged on via JOSSO");
            }

        }
    }

2. in authentication-services-context.xml we replaced the authenticationComponentImpl with


    <bean id="authenticationComponentImpl" class="org.quosis.alfresco.SimpleAcceptOrRejectAllAuthenticationComponentImpl">
        <property name="accept">
            <value>true</value>
        </property>
    </bean>

the class is:


public class SimpleAcceptOrRejectAllAuthenticationComponentImpl extends org.alfresco.repo.security.authentication.SimpleAcceptOrRejectAllAuthenticationComponentImpl {
    public NTLMMode getNTLMMode() {
        return NTLMMode.PASS_THROUGH;
    }
}

I hope I didn't forget something.