cancel
Showing results for 
Search instead for 
Did you mean: 

JBoss Portal + Alfresco - automatic SignOn+bug (part.fixed)

szimano
Champ in-the-making
Champ in-the-making
Hey,

This time I'm not going to ask, I'm going to give out something i did Smiley Happy

Please note that for now (8 Sep 2007) you have to make a small modification to alfresco source code. It's described on the end of this post.

This is a recipe on how to realise sign on for jboss portal (I used 2.6 GA) - they way it work on liferay (after you sign in portal and there's corresponding user available in alfresco db, you'll get signed in).

I based on the work did by jfernch in this post:
http://forums.alfresco.com/viewtopic.php?t=3014&highlight=sso

As a prerequisite you're going to need portal bridges available from here:
http://portals.apache.org/bridges/

After you build your alfresco from sources, using build-jboss ant directive (as described here: http://wiki.alfresco.com/wiki/Build_and_Deploy_WAR_JBossPortal )

You have to explode the war (you might leave it that way) and copy portals-bridges-portletfilter-xxx.jar to WEB-INF/lib directory.

Then we need our filter. As I said it was originally created by jfrench, i did only some modifications. You have to compile it and pack and then copy to alfresco.war/WEB-INF/lib.


package org.szimano.alfresco;

import java.io.IOException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Map;

import javax.faces.context.FacesContext;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.servlet.ServletException;
import javax.transaction.UserTransaction;

import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
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.servlet.AuthenticationHelper;
import org.alfresco.web.bean.LoginBean;
import org.alfresco.web.bean.repository.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.apache.portals.bridges.portletfilter.PortletFilter;
import org.apache.portals.bridges.portletfilter.PortletFilterChain;
import org.apache.portals.bridges.portletfilter.PortletFilterConfig;
import org.springframework.web.context.WebApplicationContext;

/**
* Filters every request for the Alfresco portlet and makes sure that the
* correct user PortletSession environment exists so that the user is not asked
* to re-authenticate
*
* <h5>Initialization Parameters</h5>
*
* <b>alfrescoExternalAuth</b>: true/false [default true]. Configures Alfresco
* to use external authentication or not. If true, certain user controls are not
* available in Alfresco
*
* @author Jon French
* @author Tomasz Szymanski
*/
public class JBossPortalAlfrescoPortletFilter implements PortletFilter {

   private static final Log LOG = LogFactory
         .getLog(JBossPortalAlfrescoPortletFilter.class);

   private AuthenticationService fAuthService;
   private AuthenticationComponent fAuthComponent;
   private PersonService fPersonService;
   private NodeService fNodeService;
   private TransactionService fTransactionService;

   private final static Logger log = Logger
         .getLogger(JBossPortalAlfrescoPortletFilter.class);

   private Boolean fAlfrescoExternalAuthentication = Boolean.TRUE;

   public void init(PortletFilterConfig filterConfig) throws PortletException {

      WebApplicationContext ctx = (WebApplicationContext) filterConfig
            .getPortletConfig()
            .getPortletContext()
            .getAttribute(
                  WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

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

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

      String alfrescoExternalAuth = filterConfig
            .getInitParameter("alfrescoExternalAuth");

      if (alfrescoExternalAuth != null) {
         fAlfrescoExternalAuthentication = Boolean
               .valueOf(alfrescoExternalAuth);
         if (LOG.isDebugEnabled()) {
            LOG.debug("Alfresco is configured to external authentication: "
                  + fAlfrescoExternalAuthentication);
         }
      }

      if (LOG.isDebugEnabled()) {
         LOG.debug(getClass().getName() + " initialized");
      }
   }

   public void renderFilter(RenderRequest request, RenderResponse response,
         PortletFilterChain chain) throws PortletException, IOException {

      if (LOG.isDebugEnabled()) {
         LOG.debug("here in renderFilter");
      }

      try {
         ensureLogin(request);
      } catch (ServletException e) {
         throw new PortletException(e);
      }

      chain.renderFilter(request, response);
   }

   public void processActionFilter(ActionRequest request,
         ActionResponse response, PortletFilterChain chain)
         throws PortletException, IOException {

      if (LOG.isDebugEnabled()) {
         LOG.debug("here in renderFilter.");
      }

      try {
         ensureLogin(request);
      } catch (ServletException e) {
         throw new PortletException(e);
      }

      chain.processActionFilter(request, response);
   }

   public void destroy() {
   }

   private void ensureLogin(PortletRequest request) throws PortletException,
         IOException, ServletException {

      PortletSession session = request.getPortletSession();

      User user = (User) session
            .getAttribute(AuthenticationHelper.AUTHENTICATION_USER);
      Principal portalUser = request.getUserPrincipal();

      /*
       * Case 1:
       *
       * The Alfresco user is NOT null, but the Portal user is null. In this
       * case, something happened (the user logged out)
       *
       * Case 2:
       *
       * The user name associated with the Alfresco session and portal session are
       * NOT the same. This can happen if the user logs out and logs in as a
       * different user.
       *
       * In either case, I need to end the Alfresco user session.
       *
       * I copied the code to end the Alfresco user's session from the
       * org.alfresco.web.bean.LoginBean.logout() method from Alfresco
       * community 1.3
       *
       */

      if ((portalUser == null && user != null) || // case 1
            (portalUser != null && user != null && !portalUser.getName()
                  .equals(user.getUserName()))) // case 2
      {

         LOG.info("Disabling alfresco user.");

         // invalidate ticket and clear the Security context for this thread
         fAuthService.invalidateTicket(user.getTicket());
         fAuthService.clearCurrentSecurityContext();
         AuthenticationHelper.invalidateAuth();
         
         session.removeAttribute(AuthenticationHelper.AUTHENTICATION_USER);

         // remove all objects from our session by hand
         // we do this as invalidating the Portal session would invalidate
         // all other portlets!

         if (FacesContext.getCurrentInstance() != null
               && FacesContext.getCurrentInstance().getExternalContext() != null) {
            Map sesMap = FacesContext.getCurrentInstance()
                  .getExternalContext().getSessionMap();

            for (Object key : sesMap.keySet()) {
               sesMap.remove(key);
            }
            
            sesMap.put(AuthenticationHelper.SESSION_INVALIDATED, true);
         }
         
         Enumeration en = request.getPortletSession().getAttributeNames();
         while (en.hasMoreElements()) {
            String key = (String)en.nextElement();
            
            if (key.endsWith(AuthenticationHelper.AUTHENTICATION_USER)) {
               request.getPortletSession().removeAttribute(key);
            }
         }

         // Set the user equal to null
         
         user = null;
         
         // every render is generated twice (render + render or action + render so we can just return)
         return;
      }

      if (user == null && portalUser != null) {
         if (LOG.isDebugEnabled()) {
            LOG.debug("session user attribute is null.");
         }

         String userName = portalUser.getName();

         /*
          * Make sure the user is loaded into the PortletSession environment.
          * The code below is modeled on the Alfresco
          * org.alfresco.web.app.servlet.NTLMAuthenticationFilter
          */

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

         try {
            tx.begin();

            // User name should match the uid in the person entry found

            fAuthComponent.setCurrentUser(userName);
            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());

            // Commit

            tx.commit();
         } catch (Throwable ex) {
            try {
               tx.rollback();
            } catch (Exception ex2) {
               log.error("Failed to rollback transaction", ex2);
            }
            if (ex instanceof RuntimeException) {
               throw (RuntimeException) ex;
            } else if (ex instanceof IOException) {
               throw (IOException) ex;
            } else if (ex instanceof ServletException) {
               throw (ServletException) ex;
            } else {
               throw new RuntimeException("Authentication setup failed",
                     ex);
            }
         }

         // Store the user

         session
               .setAttribute(AuthenticationHelper.AUTHENTICATION_USER,
                     user);

         /*
          * This parameter tells Alfresco if you are using external
          * authentication.
          *
          * If true, one thing it will do is remove the "create user" button
          *
          * Note that there seems to be a bug in Alfresco in that Alfresco
          * only checks for the presence/absence of the the
          * LoginBean.LOGIN_EXTERNAL_AUTH session attribute instead of its
          * value. This means that the attribute can equal Boolean.FALSE but
          * NOT display the "Create user" button.
          */
         if (Boolean.TRUE.equals(fAlfrescoExternalAuthentication)) {
            session.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH,
                  Boolean.TRUE);
         }

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

         if (LOG.isDebugEnabled()) {
            LOG.debug("User logged on via PortalSSO");
         }

         return;
      } else {
         if (LOG.isDebugEnabled()) {
            LOG.debug("session user attribute is NOT null: \n\n"
                  + "username: " + user.getUserName() + "\n");
         }
      }
   }

}

Last thing is to change portlet.xml so the filter is executed.

Comment out existing <portlet></portlet> section with Alfresco Client Portlet and add this


<portlet id="FilteredAlfresco">
        <portlet-name>AlfrescoClient</portlet-name>
        <display-name>Document management</display-name>
        <portlet-class>org.apache.portals.bridges.portletfilter.FilterPortlet</portlet-class>
        <init-param>
            <name>portlet-class</name>
            <value>org.alfresco.web.app.portlet.AlfrescoFacesPortlet</value>
        </init-param>
        <init-param>
            <name>default-view</name>
            <value>/jsp/browse/browse.jsp</value>
        </init-param>
        <init-param>
            <name>portlet-filters</name>
            <value>org.szimano.alfresco.JBossPortalAlfrescoPortletFilter</value>
        </init-param>
        <init-param>
            <name>org.szimano.alfresco.JBossPortalAlfrescoPortletFilter:alfrescoExternalAuth</name>
            <value>false</value>
        </init-param>
        <expiration-cache>0</expiration-cache>
        <supports>
            <mime-type>text/html</mime-type>
            <portlet-mode>VIEW</portlet-mode>
        </supports>
        <supported-locale>en</supported-locale>
        <portlet-info>
            <title>Alfresco</title>
            <short-title>This is Alfresco Portlet</short-title>
        </portlet-info>
    </portlet>

Now it should work. The solution is quite generic so should work on every JSR-168 Portal but i haven't tried it anywhere but on JBoss Portal.

The last but not least is the bug that is in alfresco. I should have started with it but as i suppose it will get fixed I'll leave it to here.

In org.alfresco.web.app.servlet.AuthenticationHelper there's a piece of code that uses ThreadLocal<String> that keeps portalUserKeyName. Unfortunately sometimes, even after logout system uses the same thread to realise request. The result is tricky - when you log in with a user, ogout and log in again some of your requests don't get authenticated (like trying to download a document) and you're asked to give credentials. And even if you do it doesn't work.

The solution, which is crappy and not elegant is to change public static User getUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse) to this


public static User getUser(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
   {
      HttpSession session = httpRequest.getSession();
      User user = null;
     
      // examine the appropriate session to try and find the User object
      if (Application.inPortalServer() == false)
      {
         user = (User)session.getAttribute(AUTHENTICATION_USER);
      }
      else
      {
         // naff solution as we need to enumerate all session keys until we find the one that
         // should match our User objects - this is weak but we don't know how the underlying
         // Portal vendor has decided to encode the objects in the session
         //if (portalUserKeyName.get() == null) <– COMMENT OUT
         //{   <– COMMENT OUT
            Enumeration enumNames = session.getAttributeNames();
            while (enumNames.hasMoreElements())
            {
               String name = (String)enumNames.nextElement();
               if (name.endsWith(AUTHENTICATION_USER))
               {
                  // cache the key value once found!
                  portalUserKeyName.set(name);
                  break;
               }
            }
         //} <– COMMENT OUT
         if (portalUserKeyName.get() != null)
         {
            user = (User)session.getAttribute(portalUserKeyName.get());
         }
      }
     
      return user;
   }

That way it will always look for a user in session and will authenticate properly.

That's it. Hope it helps those who want to play with alfresco on JBoss Portal.

Cheers,
Tomek
1 REPLY 1

califa198
Champ in-the-making
Champ in-the-making
sorry to bother but i have a little problem with the SSO implementation above in Alfresco. Lets see if anyone can help. My IDE tells me that :

file org\alfresco\error\AlfrescoRuntimeException.class not found

I have searched all over my computer for it and cant seem to be able to find it, i have searched the web, and i found a .java implementation of the file that i tried to compile but again i have another problem and that is that:

package org.alfresco.i18n does not exist.

can someone show me somewhere where i could download that package, i have been trying to find it for over an hour and im unable to find it. Any other solution would be great too. thanks