cancel
Showing results for 
Search instead for 
Did you mean: 

Custom personService with CAS authentication

migueletto
Champ in-the-making
Champ in-the-making
Hello,

I have Alfresco Community 2.1 running in Tomcat 6. Using the local admin account, I was able
to create other users and everything works fine.

The next step was trying to integrate the Alfresco web client with CAS to provide SSO in our
environment. I used HTTPRequestAuthenticationFilter as a starting point and developed a
CASAuthenticationFilter that extracts the authenticated login name from the session.
After protecting the Alfresco URL path with CAS, this is what happens:

1. User access the http://somehost/alfresco.
2. User is redirected to CAS login page.
3. User authenticates with CAS and is redirected back to Alfresco.
4. My CASAuthenticationFilter executes and extracts the login information, authenticating with Alfresco.

Now users are able to authenticate with an external service (CAS), but
I still have to create local users in Alfresco using admin, so that users can log in.

Now my question: is it possible to have Alfresco extract user information from an external
source (like Active Directory - AD) so that I do not have to duplicate our entire user base in Alfresco ?

I have read in the wiki that you can synchronize the user base in Alfresco with LDAP, but this
is not exactly what I want. I do not want to store unnecessary user information inside the Alfresco database.

In fact, I already have in place another Filter that runs after the CAS filter and before Alfresco.
This filter connects to AD and get user information using the CAS login as a key. This way, after
I get to the first code inside Alfresco, I already have stored in my HttpSession a JavaBean
with:

- User login (can also be used as homefolder).
- User first and last name.
- User email address.
- Organization ID.

I was thinking in maybe creating a "personService" that reads this
information instead of accessing a database (I do not plan to offer the user an option to change
his details from within Alfresco web interface, this will be done only in AD. So this "personService"
would be read-only).
And I believe I would also need to create the initial user space the first time he logs in
(because of the way I configured the filter, if the user got to Alfresco, he has authorization
to use the application. This is why I need automatic space creation).

Of course this approach has some limitations. This service would not be able to respond to
a getAllPeople call. Unless it uses a hybrid approach, that is, creates users on demand after
their first login.

I am really new to Alfresco, so I do not know if this makes sense.
If anyone has advice on this it would be very helpfull:

- How/where to create/configure a custom "personService".
- How/where to insert the logic of automatic user/space creation.

Thanks,
Marcio.
6 REPLIES 6

iblanco
Confirmed Champ
Confirmed Champ
I've been trying to conect Alfresco to CAS but I've not been able to achieve it.

I've read the reference guide about ACEGI and tried to add the required beans, but no way. From your post it sounds like it shouldn't be such a big deal ( well, apart from the fact that you had to code your own authenticator ).

Could you please post the configuration files you had to change ? ( web.xml ? authentication-services-context.xml ? public-services-security-context.xml ? ). It would be great if you could also post the sourcecode of the class you created or at least explain how it works.

There isn't any good information about Alfresco Authentication with CAS so it would be really helpful for many people.

Thank you very much.

migueletto
Champ in-the-making
Champ in-the-making
Hello,

This is what I am doing. In the Alfresco web.xml I added three filters. The first one is the standard CAS filter found in casclient.jar (I am using cas-client-2.0.11.zip distribution), where xxx is my internal hostname:

  <filter>
    <filter-name>CAS</filter-name>
    <filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>

    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
      <para-name>
      <param-value>https://xxx/cas/login</param-value>
    </init-param>

    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
      <param-value>https://xxx/cas/serviceValidate</param-value>
    </init-param>

    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.wrapRequest</param-name>
      <param-value>true</param-value>
    </init-param>

    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
      <param-value>xxx</param-value>
    </init-param>
  </filter>

The second one is my custom filter. It extracts the user login information from the session and queries an Active Directory server to get the remaining information (currently first name, last name and email address). The reason I build this filter is because I wanted a central component, shared by many applications, that is able to query a common user directory. It is not necessary if you only want to authenticate with CAS:

  <filter>
    <filter-name>Autenticacao</filter-name>
    <filter-class>x.y.z.FiltroAutenticacao</filter-class>
  </filter>

I also changed the existing Authentication Filter to use the filter I built using HttprequestAuthenticationFilter as a base (named SessionAuthenticationFilter):

   <filter>
      <filter-name>Authentication Filter</filter-name>
      <!– <filter-class>org.alfresco.web.app.servlet.AuthenticationFilter</filter-class> –>
      <filter-class>org.alfresco.web.app.servlet.SessionAuthenticationFilter</filter-class>
  </filter>

And chained them together (the order is important):

   <filter-mapping>
      <filter-name>CAS</filter-name>
      <url-pattern>/faces/*</url-pattern>
   </filter-mapping>

   <filter-mapping>
      <filter-name>Autenticacao</filter-name>
      <url-pattern>/faces/*</url-pattern>
   </filter-mapping>

   <filter-mapping>
      <filter-name>Authentication Filter</filter-name>
      <url-pattern>/faces/*</url-pattern>
   </filter-mapping>

In short: the first filter authenticates with CAS and stores the login in the HttpSession. The second retrieves the login from the HttpSession and queries AD, storing an custom user bean in the HttpSession (this is done only once per session). The third retrieves the user bean from HttpSession and authenticates with Alfresco. It is at this point that I do not know how to use the attributes I already have to automatically create the Alfresco account. I also had to change the way HttprequestAuthenticationFilter was performing authentication, since it was giving an "AuthenticationException: Could not find user by userName" when it was calling AbstractAuthenticationComponent.setCurrentUser().

I will try to post the code here or somewhere else. But like I said, beeing new to Alfresco I suspect there is a simpler way to achieve this.

Regards,
Marcio.

margas
Champ in-the-making
Champ in-the-making
Hi,
I have done the same thing to handle single sign one with Oracle Single Sign one Server(OSSO) but i get the same exception when i try to login: "AuthenticationException: Could not find user by userName", but the user is authenticated correctly in alfresco.
You said 
-"I also had to change the way HttprequestAuthenticationFilter was performing authentication"
can you please provide the changes so that to remove this exception?

I can also explain (and give an example)the way i used to single sign one Alfresco with Oracle Portal in case someone wants to try it.
For me it works perfect… Smiley Happy

thank you

shmc
Champ in-the-making
Champ in-the-making
Hi margas,

I'm trying to hookup Oracle Single Sign On Server with Alfresco but I don't even know where to begin.
Could you post the steps you had to take to make it work?

Thanks in advance.

margas
Champ in-the-making
Champ in-the-making
Sorry for answering after 6 months but this may help other members


In the web.xml add the following filter


<filter>
   <filter-name>Osso Filter</filter-name>
   <filter-class>org.alfresco.web.app.servlet.custom.OSSOAuthenticationFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>Osso Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>




#################Class file######################


/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception.  You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing
*/
package org.alfresco.web.app.servlet.custom;

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.app.servlet.AbstractAuthenticationFilter;
import org.alfresco.web.app.servlet.AuthenticationHelper;
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 org.alfresco.web.bean.repository.Repository;



public class OSSOAuthenticationFilter 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(OSSOAuthenticationFilter.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 OSSOAuthenticationFilter() {
      super();
   }

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

   /**
    * Run the filter
    *
    * @param sreq
    *            ServletRequest
    * @param sresp
    *            ServletResponse
    * @param chain
    *            FilterChain
    * @exception IOException
    * @exception ServletException
    */
   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);

      String userName = null;
      //Get headers setted by the oracle sigle sign one server
      java.util.Enumeration reqMap = req.getHeaders("Osso-User-Dn");
      
      if (reqMap == null) {
         logger.error("No user logged in");
      } else {
         while (reqMap.hasMoreElements()){
            //Get from the full dn the username
            userName  = ((String)reqMap.nextElement()).split(",")[0].trim().toString().split("=")[1].trim().toString();
            //String tmp = value.split(",")[0].trim().toString();
            //userName = tmp.split("=")[1].trim().toString();
         }
      }

      if (logger.isDebugEnabled()) {
         logger.debug("OSSO : 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("OSSO : User " + user.getUserName() + " validate ticket");

            if (user.getUserName().equals(userName)) {
               UserTransaction tx1 = transactionService.getUserTransaction();
               try {
                  tx1.begin();
                  authComponent.setCurrentUser(user.getUserName());
                  tx1.commit();
               }catch(Exception ex){
                  logger.error("Failed due to transaction " + ex);
                  try {
                     tx1.rollback();
                  } catch (Exception ex2) {
                     logger.error("Failed to rollback transaction", ex2);
                  }

               }
               I18NUtil.setLocale(Application.getLanguage(httpSess));
               chain.doFilter(sreq, sresp);
               return;
            } else {
               // No match
               //setAuthenticatedUser(req, httpSess, userName);
                  resp.sendRedirect("http://hyperion:7778/alfresco");
                  return;
            }
         } 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
        {
            resp.sendRedirect("http://hyperion:7778/alfresco");
            //chain.doFilter(sreq, sresp);
            return;
        }
       
   }

   /**
    * Set the authenticated user.
    *
    * It does not check that the user exists at the moment.
    *
    * @param req
    * @param httpSess
    * @param userName
    */
   private void setAuthenticatedUser(HttpServletRequest req, HttpSession httpSess, String userName) {
      if (userName != null){
         UserTransaction tx1 = transactionService.getUserTransaction();
         // Set the authentication
         try {
            tx1.begin();
         authComponent.setCurrentUser(userName);
         tx1.commit();
         } catch (Throwable ex) {
            logger.error(ex);
            try {
               tx1.rollback();
            } catch (Exception ex2) {
               logger.error("Failed to rollback transaction", ex2);
            }
         }
   
         // 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);
            if(homeSpaceRef == null) {
               logger.warn("Home Folder is null for user '"+userName+"', using company_home.");
               homeSpaceRef = (NodeRef) nodeService.getRootNode(Repository.getStoreRef());
            }
            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();
   }

   /**
    * Return the login page address
    *
    * @return String
    */
   private String getLoginPage() {
      if (loginPage == null) {
         loginPage = Application.getLoginPage(context);
      }

      return loginPage;
   }
}




##############Oracle############
1st step
$ORACLE_HOME/Apache/Apache/conf/httpd.conf, add the following entries:
ProxyPass /alfresco/ http://iishostSmiley Tongueort/alfresco/

ProxyPass /alfresco http://iishostSmiley Tongueort/alfresco/


ProxyPassReverse /alfresco/ http://iishostSmiley Tongueort/alfresco/

ProxyPassReverse /alfresco http://iishostSmiley Tongueort/alfresco/


2nd step
The second step is to set up Oracle SSO to protect the application's URL. Using EM console or by directly editing $ORACLE_HOME/Apache/Apache/conf/mod_osso.conf, add the following lines just before the </IfModule>:
    <Location /alfresco>
        require valid-user
        AuthType Basic
    </Location>
    <Location /alfresco*>
        require valid-user
        AuthType Basic
    </Location>

3rd step
It is important to restart Apache after the configuration



thank you

jethwanimanoj
Champ in-the-making
Champ in-the-making
Hi,

I'm using Alfresco version 3.4d , Could you help me with the path where to put the class file ?

thanks

Manoj