From 588ab10191a99b19a3a6aab1aaf856cf26a034eb Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Tue, 11 Sep 2007 04:18:53 +0000 Subject: [PATCH] Merged V2.1 to HEAD 6636: Temporary hack to fix build. 6637: Better handling of binary string bufs, disable link validation when poll interval is <= 0 6638: Forgotten files for TXT to PDF transformer. 6639: Fix for AWC-1541 6641: Fix for WCM-792. 6642: A little extra PropertyValue support for createNode, too. 6643: Fix for WCM-791 6644: Closure of AR-1528: Check concurrency handling of DuplicateChildNodeNameException 6647: Fix WCM-794 6648: WCM-656 6650: Applied user supplied patch to fix AWC-1546 - Cannot mount AVM using CIFS on new alfresco installation. 6651: Index tidy ups 6654: Various minor updates for passthru authentication debugging and error handling. 6657: Fix for WCM-799 (Some items selected for submission were not present) 6659: Updated installers. 6660: Partial fix to AWC-1524 6661: Fix WCM-803 6664: Including hibernate-3.2.1.jar in $VIRTUAL_TOMCAT_HOME/server/lib/ 6665: adding an automated unit test for output path patterns. 6668: Fixed to add shale-test-1.0.4.jar to Eclipse classpath (PHH oked) 6681: Fixes WCM-811 - Lookup.getIndirectionPath() had a bit of a howler in it. 6684: UncategorizedSQLException with the word 'deadlock' in the message is now cause for retrying a transaction. 6691: Fix for WCM-813 (lock not removed when expiration date set and no workflow on web project) 6696: Imporved SSO filters for SiteMinder etc + test filter 6697: Support for scheduled import 6699: Fix for the compliation target: compile-benchmarkframework 6701: Fix for 1.6 JVMs (1.5 gets by with lucky ordering) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6749 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../auth/HTTPRequestAuthenticationFilter.java | 418 ++++++++++++++++++ ...RequestAuthenticationFilterTestFilter.java | 138 ++++++ 2 files changed, 556 insertions(+) create mode 100644 source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilter.java create mode 100644 source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilterTestFilter.java diff --git a/source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilter.java new file mode 100644 index 0000000000..bf74785c86 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilter.java @@ -0,0 +1,418 @@ +/* + * 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.repo.webdav.auth; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +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.transaction.UserTransaction; + +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.InvalidNodeRefException; +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.NoSuchPersonException; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +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; + +/** + * WebDAV Authentication Filter Class for SSO linke SiteMinder and IChains + */ +public class HTTPRequestAuthenticationFilter implements Filter +{ + // Debug logging + + private static Log logger = LogFactory.getLog(HTTPRequestAuthenticationFilter.class); + + // Authenticated user session object name + + public final static String AUTHENTICATION_USER = "_alfDAVAuthTicket"; + + // Allow an authenitcation ticket to be passed as part of a request to bypass authentication + + private static final String ARG_TICKET = "ticket"; + + // Servlet context + + private ServletContext m_context; + + // Various services required by NTLM authenticator + + private AuthenticationService m_authService; + + private PersonService m_personService; + + private NodeService m_nodeService; + + private TransactionService m_transactionService; + + private String httpServletRequestAuthHeaderName; + + private AuthenticationComponent m_authComponent; + + // By default match everything if this is not set + private String m_authPatternString = null; + + private Pattern m_authPattern = null; + + /** + * Initialize the filter + * + * @param config + * FitlerConfig + * @exception ServletException + */ + public void init(FilterConfig config) throws ServletException + { + // Save the context + + m_context = config.getServletContext(); + + // Setup the authentication context + + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); + + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + m_nodeService = serviceRegistry.getNodeService(); + m_authService = serviceRegistry.getAuthenticationService(); + m_transactionService = serviceRegistry.getTransactionService(); + m_personService = (PersonService) ctx.getBean("PersonService"); // transactional and permission-checked + m_authComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + + httpServletRequestAuthHeaderName = config.getInitParameter("httpServletRequestAuthHeaderName"); + if(httpServletRequestAuthHeaderName == null) + { + httpServletRequestAuthHeaderName = "x-user"; + } + this.m_authPatternString = config.getInitParameter("authPatternString"); + if (this.m_authPatternString != null) + { + try + { + m_authPattern = Pattern.compile(this.m_authPatternString); + } + catch (PatternSyntaxException e) + { + logger.warn("Invalid pattern: " + this.m_authPatternString, e); + m_authPattern = null; + } + } + + } + + /** + * Run the authentication filter + * + * @param req + * ServletRequest + * @param resp + * ServletResponse + * @param chain + * FilterChain + * @exception ServletException + * @exception IOException + */ + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, + ServletException + { + // Assume it's an HTTP request + + HttpServletRequest httpReq = (HttpServletRequest) req; + HttpServletResponse httpResp = (HttpServletResponse) resp; + + // Get the user details object from the session + + WebDAVUser user = (WebDAVUser) httpReq.getSession().getAttribute(AUTHENTICATION_USER); + + if (user == null) + { + // Check for the auth header + + String authHdr = httpReq.getHeader(httpServletRequestAuthHeaderName); + if (logger.isDebugEnabled()) + { + if (authHdr == null) + { + logger.debug("Header not found: " + httpServletRequestAuthHeaderName); + } + else + { + logger.debug("Header is <" + authHdr + ">"); + } + } + + // Throw an error if we have an unknown authentication + + if ((authHdr != null) || (authHdr.length() > 0)) + { + + // Get the user + + String userName = ""; + if (m_authPattern != null) + { + Matcher matcher = m_authPattern.matcher(authHdr); + if (matcher.matches()) + { + userName = matcher.group(); + if ((userName == null) || (userName.length() < 1)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Extracted null or empty user name from pattern " + + m_authPatternString + " against " + authHdr); + } + reject(httpReq, httpResp); + return; + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("no pattern match for " + m_authPatternString + " against " + authHdr); + } + reject(httpReq, httpResp); + return; + } + } + else + { + userName = authHdr; + } + + if (logger.isDebugEnabled()) + { + logger.debug("User = " + userName); + } + + // Get the authorization header + + UserTransaction tx = null; + try + { + tx = m_transactionService.getUserTransaction(); + tx.begin(); + // Authenticate the user + + m_authComponent.setCurrentUser(userName); + + // Get the user node and home folder + + NodeRef personNodeRef = m_personService.getPerson(userName); + NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, + ContentModel.PROP_HOMEFOLDER); + + // Setup User object and Home space ID etc. + + user = new WebDAVUser(userName, m_authService.getCurrentTicket(), homeSpaceRef); + + tx.commit(); + tx = null; + + httpReq.getSession().setAttribute(AUTHENTICATION_USER, user); + } + catch (AuthenticationException ex) + { + if(logger.isDebugEnabled()) + { + logger.debug("Failed", ex); + } + user = null; + // Do nothing, user object will be null + } + catch (NoSuchPersonException e) + { + // Do nothing, user object will be null + if(logger.isDebugEnabled()) + { + logger.debug("Failed", e); + } + user = null; + } + catch (Exception e) + { + // Do nothing, user object will be null + if(logger.isDebugEnabled()) + { + logger.debug("Failed", e); + } + user = null; + } + finally + { + try + { + if (tx != null) + { + tx.rollback(); + } + } + catch (Exception tex) + { + } + } + } + else + { + // Check if the request includes an authentication ticket + + String ticket = req.getParameter(ARG_TICKET); + + if (ticket != null && ticket.length() > 0) + { + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Logon via ticket from " + + req.getRemoteHost() + " (" + req.getRemoteAddr() + ":" + req.getRemotePort() + ")" + + " ticket=" + ticket); + + UserTransaction tx = null; + try + { + // Validate the ticket + + m_authService.validate(ticket); + + // Need to create the User instance if not already available + + String currentUsername = m_authService.getCurrentUserName(); + + // Start a transaction + + tx = m_transactionService.getUserTransaction(); + tx.begin(); + + NodeRef personRef = m_personService.getPerson(currentUsername); + user = new WebDAVUser(currentUsername, m_authService.getCurrentTicket(), personRef); + NodeRef homeRef = (NodeRef) m_nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); + + // Check that the home space node exists - else Login cannot proceed + + if (m_nodeService.exists(homeRef) == false) + { + throw new InvalidNodeRefException(homeRef); + } + user.setHomeNode(homeRef); + + tx.commit(); + tx = null; + + // Store the User object in the Session - the authentication servlet will then proceed + + httpReq.getSession().setAttribute(AUTHENTICATION_USER, user); + } + catch (AuthenticationException authErr) + { + // Clear the user object to signal authentication failure + if(logger.isDebugEnabled()) + { + logger.debug("Failed", authErr); + } + user = null; + } + catch (Throwable e) + { + // Clear the user object to signal authentication failure + if(logger.isDebugEnabled()) + { + logger.debug("Failed", e); + } + user = null; + } + finally + { + try + { + if (tx != null) + { + tx.rollback(); + } + } + catch (Exception tex) + { + } + } + } + } + + // Check if the user is authenticated, if not then prompt again + + if (user == null) + { + // No user/ticket, force the client to prompt for logon details + reject(httpReq, httpResp); + return; + } + } + else + { + // Setup the authentication context + + m_authService.validate(user.getTicket()); + } + + // Chain other filters + + chain.doFilter(req, resp); + } + + private void reject( HttpServletRequest httpReq, HttpServletResponse httpResp) throws IOException + { + httpResp.setHeader("WWW-Authenticate", "BASIC realm=\"Alfresco DAV Server\""); + httpResp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpResp.flushBuffer(); + return; + } + + /** + * Cleanup filter resources + */ + public void destroy() + { + // Nothing to do + } +} diff --git a/source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilterTestFilter.java b/source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilterTestFilter.java new file mode 100644 index 0000000000..ae855aacc5 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/auth/HTTPRequestAuthenticationFilterTestFilter.java @@ -0,0 +1,138 @@ +/* + * 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.repo.webdav.auth; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +/** + * This filter simply sets a request header to test the SSO filters + */ +public class HTTPRequestAuthenticationFilterTestFilter implements Filter +{ + + private String httpServletRequestAuthHeaderName; + + private String userName; + + /** + * Initialize the filter + * + * @param config + * FitlerConfig + * @exception ServletException + */ + public void init(FilterConfig config) throws ServletException + { + httpServletRequestAuthHeaderName = config.getInitParameter("httpServletRequestAuthHeaderName"); + if (httpServletRequestAuthHeaderName == null) + { + httpServletRequestAuthHeaderName = "x-user"; + } + + userName = config.getInitParameter("userName"); + if (userName == null) + { + userName = "guest"; + } + } + + /** + * Run the authentication filter + * + * @param req + * ServletRequest + * @param resp + * ServletResponse + * @param chain + * FilterChain + * @exception ServletException + * @exception IOException + */ + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException + { + // Assume it's an HTTP request + + HttpServletRequest httpReq = (HttpServletRequest) req; + chain.doFilter(getProxy(httpReq), resp); + } + + /** + * Cleanup filter resources + */ + public void destroy() + { + // Nothing to do + } + + private HttpServletRequest getProxy(HttpServletRequest req) + { + HttpServletRequest proxy = (HttpServletRequest) Proxy.newProxyInstance(HttpServletRequest.class.getClassLoader(), new Class[] { HttpServletRequest.class }, + new Handler(req)); + return proxy; + } + + class Handler implements InvocationHandler + { + HttpServletRequest httpReq; + + Handler(HttpServletRequest httpReq) + { + this.httpReq = httpReq; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + if (method.getName().equals("getHeader")) + { + Object arg0 = args[0]; + if (arg0 != null) + { + if (arg0 instanceof String) + { + String headerName = (String) arg0; + if (headerName.equals(httpServletRequestAuthHeaderName)) + { + return userName; + } + } + } + } + return method.invoke(httpReq, args); + } + + } +}