As we transition to the new Hyland Community, some post links may be temporarily disrupted as we finalize redirects. We apologize for the inconvenience and are working to resolve this as quickly as possible. Details.
Hi, we are also interested in the use of Shibboleth with Alfresco. Have you made some progress on this ?Denis
package org.alfresco.web.app.servlet; import org.alfresco.model.ContentModel; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.webservice.administration.NewUserDetails; import org.alfresco.repo.webservice.types.NamedValue; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.web.bean.LoginBean; import org.alfresco.web.bean.repository.User; import org.alfresco.web.app.Application; 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 javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.transaction.UserTransaction; import javax.transaction.SystemException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.*; /** * * * @author Kimmo Ilppola */ public class ShibbolethHTTPRequestAuthenticationFilter extends AbstractAuthenticationFilter implements Filter { /** Location of configuration file on classpath */ private final static String PROPERTY_FILE = "/shibbolethAttributes.properties"; private static Properties CONFIG_PROPERTIES; /* Name of header properties */ private final static String SHIB_USER = "header.username"; private final static String FIRSTNAME_HEADER_NAME_PROPERTY = "header.firstname"; private final static String LASTNAME_HEADER_NAME_PROPERTY = "header.lastname"; private final static String ORGID_HEADER_NAME_PROPERTY = "header.orgId"; private final static String EMAIL_HEADER_NAME_PROPERTY = "header.email"; private final static String LOGOUT_URL_HEADER_NAME_PROPERTY = "header.logout-url"; private static String shibUserNameHeaderName = "Shib-InetOrgPerson-uid"; private static String firstNameHeaderName = "Shib-InetOrgPerson-givenName"; private static String lastNameHeaderName = "Shib-InetOrgPerson-cn"; private static String emailHeaderName = "Shib-InetOrgPerson-mail"; private static String orgIdNumHeaderName = "Shib-InetOrgPerson-ou"; private static String logoutUrlHeaderName = "logout-url"; private static Log logger = LogFactory.getLog(ShibbolethHTTPRequestAuthenticationFilter.class); private ServletContext context; private AuthenticationComponent authComponent; private AuthenticationService authService; private TransactionService transactionService; private PersonService personService; private NodeService nodeService; private MutableAuthenticationDao authenticationDao; private SearchService searchService; private String loginPage = null; public ShibbolethHTTPRequestAuthenticationFilter() { super(); if (logger.isDebugEnabled()) { logger.debug("Initializing shibboleth authenticator using property file " + PROPERTY_FILE); } InputStream propsIn = ShibbolethHTTPRequestAuthenticationFilter.class.getResourceAsStream(PROPERTY_FILE); CONFIG_PROPERTIES = new Properties(); try { CONFIG_PROPERTIES.load(propsIn); shibUserNameHeaderName = CONFIG_PROPERTIES.getProperty(SHIB_USER); firstNameHeaderName = CONFIG_PROPERTIES.getProperty(FIRSTNAME_HEADER_NAME_PROPERTY); lastNameHeaderName = CONFIG_PROPERTIES.getProperty(LASTNAME_HEADER_NAME_PROPERTY); emailHeaderName = CONFIG_PROPERTIES.getProperty(EMAIL_HEADER_NAME_PROPERTY); orgIdNumHeaderName = CONFIG_PROPERTIES.getProperty(ORGID_HEADER_NAME_PROPERTY); logoutUrlHeaderName = CONFIG_PROPERTIES.getProperty(LOGOUT_URL_HEADER_NAME_PROPERTY); } catch (IOException e) { logger.warn("ShibbolethHTTPRequestAuthenticationFilter is unable to read properties file, using default properties", e); } } 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 authHdr = req.getHeader( shibUserNameHeaderName ); if(logger.isDebugEnabled()) { logger.debug("ShibbolethHTTPRequestAuthenticationFilter uri: "+req.getRequestURI()+", authHdr: "+authHdr); } // Throw an error if we have an unknown authentication if ((authHdr == null) || (authHdr.length() < 1)) { resp.sendRedirect(req.getContextPath() + "/jsp/noaccess.jsp"); BaseServlet.redirectToLoginPage((HttpServletRequest)sreq, (HttpServletResponse)sresp, context); chain.doFilter(sreq, sresp); return; } // redirect login/logout to shibboleth logout url // todo: this doesn't work yet if (req.getRequestURI().endsWith(getLoginPage())) { resp.sendRedirect( req.getHeader(logoutUrlHeaderName) ); return; } // 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. Shibboleth allready validates the ticket so we just need to check the userName // todo : is this validation reduntant? if (user.getUserName().equals(authHdr)) { authComponent.setCurrentUser(user.getUserName()); chain.doFilter(sreq, sresp); return; } else { // No match setAuthenticatedUser(req, httpSess, authHdr); } } catch (Throwable ex) { if (logger.isErrorEnabled()) logger.error("Failed to validate user " + user.getUserName(), ex); } } setAuthenticatedUser(req, httpSess, authHdr); chain.doFilter(sreq, sresp); return; } /** * Set the authenticated user. * * @param req * @param httpSess * @param userName */ private void setAuthenticatedUser(HttpServletRequest req, HttpSession httpSess, String userName) { // shibboleth does not provide any other means to import users addShibUser(req,httpSess); UserTransaction atx = transactionService.getUserTransaction(); try { atx.begin(); // Set the authentication authComponent.setCurrentUser(userName); atx.commit(); } catch (Throwable e) { logger.error(e); try { atx.rollback(); } catch (SystemException e1) { logger.error("Failed to rollback transaction", e); } } // Set up the user information // Note that we have to create users here as we have no other way of retrieving user-attributes 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); } protected NodeRef getCompanyHome() { StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); ResultSet resultSet = searchService.query(storeRef, SearchService.LANGUAGE_LUCENE, "PATH:\"/app:company_home\""); return resultSet.getNodeRef(0); } /** * @return The login page url */ private String getLoginPage() { if (this.loginPage == null) { this.loginPage = Application.getLoginPage(this.context); } return this.loginPage; } private boolean addShibUser(HttpServletRequest req, HttpSession httpSess) { // get all properties from header String userName = req.getHeader( shibUserNameHeaderName ); String orgId = req.getHeader(orgIdNumHeaderName); String firstName = req.getHeader(firstNameHeaderName); String lastName = req.getHeader(lastNameHeaderName); String email = req.getHeader( emailHeaderName ); // Create a new authentication try { // todo: passwords should not be stored! authService.createAuthentication(userName, "90wy3vgyn0w394vf".toCharArray()); } catch (AuthenticationException e) { logger.info( "User allready exists, authenticating.."); return false; } finally { UserTransaction atx = transactionService.getUserTransaction(); try { atx.begin(); // Set the authentication // todo: can we do this otherwise? we are not yet authenticated authComponent.setCurrentUser("admin"); atx.commit(); } catch (Throwable e) { logger.error(e); try { atx.rollback(); } catch (SystemException e1) { logger.error("Failed to rollback transaction", e); } } // todo: use homeFolderProvider String homeFolder = "workspace://SpacesStore/"+getCompanyHome().getId(); // create new userDetails object NewUserDetails newUser = new NewUserDetails(); logger.info("newuser.setUserName :"+userName); newUser.setUserName(userName); newUser.setPassword("shibboleth"); newUser.setProperties( createPersonProperties( homeFolder , firstName, "", lastName, email, orgId) ); if(!personService.personExists(newUser.getUserName())) { atx = transactionService.getUserTransaction(); try { atx.begin(); // Create a new user authenticationDao.createUser(newUser.getUserName(),"shibboleth".toCharArray()); // Create a new person Map<QName, Serializable> properties = new HashMap<QName, Serializable>(7); logger.info("properties.put username : "+newUser.getUserName()); properties.put(ContentModel.PROP_USERNAME, newUser.getUserName()); for (NamedValue namedValue : newUser.getProperties()) { logger.info("properties.put "+namedValue.getName()+" : "+namedValue.getValue()); properties.put(QName.createQName(namedValue.getName()), namedValue.getValue()); } NodeRef personNodeRef = personService.createPerson(properties); atx.commit(); } catch (Throwable e) { logger.error(e); try { atx.rollback(); } catch (SystemException e1) { logger.error("Failed to rollback transaction", e); } } } else { logger.info("User "+userName+" exists, updating.."); atx = transactionService.getUserTransaction(); try { atx.begin(); Map<QName, Serializable> properties = new HashMap<QName, Serializable>(7); logger.info("properties.put username : "+newUser.getUserName()); properties.put(ContentModel.PROP_USERNAME, newUser.getUserName()); for (NamedValue namedValue : newUser.getProperties()) { logger.info("properties.put "+namedValue.getName()+" : "+namedValue.getValue()); properties.put(QName.createQName(namedValue.getName()), namedValue.getValue()); } personService.setPersonProperties(userName,properties); atx.commit(); } catch (Throwable e) { logger.error(e); try { atx.rollback(); } catch (SystemException e1) { logger.error("Failed to rollback transaction", e); } } } authService.clearCurrentSecurityContext(); return true; } } private NamedValue[] createPersonProperties( String homeFolder, String firstName, String middleName, String lastName, String email, String orgId) { // Create the new user objects return new NamedValue[] { new NamedValue("{http://www.alfresco.org/model/content/1.0}homeFolder", false, homeFolder, null), new NamedValue("{http://www.alfresco.org/model/content/1.0}firstName", false, firstName, null), new NamedValue("{http://www.alfresco.org/model/content/1.0}middleName", false, middleName, null), new NamedValue("{http://www.alfresco.org/model/content/1.0}lastName", false, lastName, null), new NamedValue("{http://www.alfresco.org/model/content/1.0}email", false, email, null), new NamedValue("{http://www.alfresco.org/model/content/1.0}organizationId", false, orgId, null) }; } 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"); authenticationDao = (MutableAuthenticationDao) ctx.getBean("authenticationDao"); searchService = (SearchService) ctx.getBean("SearchService"); } }
# Take note how you want shibboleth attributess mapped to alfresco # These need to match parameters defined in AAP.xml (attribute acceptance policy) # logout-url is the page that should be displayed upon logout (invalidating shibd-session is not yet implemented) header.username = Shib-InetOrgPerson-uid header.firstname = Shib-InetOrgPerson-givenName header.lastname = Shib-InetOrgPerson-cn header.email = Shib-InetOrgPerson-mail header.orgId = Shib-InetOrgPerson-ou header.logout-url = logout-url