From ae92254069111291cee3439a1718b11074d6fc35 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Thu, 18 Dec 2008 11:51:12 +0000 Subject: [PATCH] Merged V3.0 to HEAD 11829: Updated javadocs for RuntimeExec class 11830: Updated and wired in Spring source 11831: Fixed ETHREEOH-382: Can't run Lucene search via Node Browser 11832: Added unit test for V3.0 rev 11535 11834: Removed redundant TODO item 11835: ETHREEOH-798 Double clicking OK on most pop-up dialogs in Share causing multiple requests to be sent - and errors generated for the user 11836: Fix for a number of session based authentication and webscript authentication issues with NTLM from Share. Fixes ETHREEOH-806 and ETHREEOH-834 and first part of fix for ETHREEOH-789. 11838: Sharepoint Protocol Support 11843: Build fix 11846: Refactor of the SSO web filters (NTLM and Kerberos) for web-client and WebDAV. 11848: Added commented out entries for web-client and WebDAV Kerberos filter debugging. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12483 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/repository/touch.get.desc.xml | 2 +- .../BaseKerberosAuthenticationFilter.java | 612 +++++++++++++ .../auth/BaseNTLMAuthenticationFilter.java | 414 +++++---- .../auth/BaseSSOAuthenticationFilter.java | 580 ++++++++++++ .../auth/KerberosAuthenticationFilter.java | 831 +----------------- .../webdav/auth/NTLMAuthenticationFilter.java | 284 +----- 6 files changed, 1449 insertions(+), 1274 deletions(-) create mode 100644 source/java/org/alfresco/repo/webdav/auth/BaseKerberosAuthenticationFilter.java create mode 100644 source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/touch.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/touch.get.desc.xml index 42a2e8263f..004b066fe2 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/touch.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/touch.get.desc.xml @@ -3,6 +3,6 @@ SSO authentication touch point - return a simple 200 OK status /touch argument - none + guest none \ No newline at end of file diff --git a/source/java/org/alfresco/repo/webdav/auth/BaseKerberosAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/BaseKerberosAuthenticationFilter.java new file mode 100644 index 0000000000..d99a389be7 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/auth/BaseKerberosAuthenticationFilter.java @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2006-2008 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.net.InetAddress; +import java.net.UnknownHostException; +import java.security.Principal; +import java.util.Vector; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.sasl.RealmCallback; +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; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.alfresco.jlan.server.auth.kerberos.KerberosDetails; +import org.alfresco.jlan.server.auth.kerberos.SessionSetupPrivilegedAction; +import org.alfresco.jlan.server.auth.spnego.NegTokenInit; +import org.alfresco.jlan.server.auth.spnego.NegTokenTarg; +import org.alfresco.jlan.server.auth.spnego.OID; +import org.alfresco.jlan.server.auth.spnego.SPNEGO; +import org.alfresco.repo.SessionUser; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.apache.commons.codec.binary.Base64; +import org.ietf.jgss.Oid; + +/** + * Base class with common code and initialisation for Kerberos authentication filters. + * + * @author gkspencer + */ +public abstract class BaseKerberosAuthenticationFilter extends BaseSSOAuthenticationFilter implements CallbackHandler { + + // Constants + // + // Default login configuration entry name + + private static final String LoginConfigEntry = "AlfrescoHTTP"; + + // Kerberos settings + // + // Account name and password for server ticket + // + // The account name must be built from the HTTP server name, in the format :- + // + // HTTP/@ + + private String m_accountName; + private String m_password; + + // Kerberos realm and KDC address + + private String m_krbRealm; + private String m_krbKDC; + + // Login configuration entry name + + private String m_loginEntryName = LoginConfigEntry; + + // Server login context + + private LoginContext m_loginContext; + + // SPNEGO NegTokenInit blob, sent to the client in the SMB negotiate response + + private byte[] m_negTokenInit; + + /** + * Initialize the filter + * + * @param args FilterConfig + * @exception ServletException + */ + public void init(FilterConfig args) throws ServletException + { + // Call the base SSO filter initialization + + super.init( args); + + // Check if Kerberos is enabled, get the Kerberos KDC address + + String kdcAddress = args.getInitParameter("KDC"); + + if (kdcAddress != null && kdcAddress.length() > 0) + { + // Set the Kerberos KDC address + + m_krbKDC = kdcAddress; + + // Get the Kerberos realm + + String krbRealm = args.getInitParameter("Realm"); + if ( krbRealm != null && krbRealm.length() > 0) + { + // Set the Kerberos realm + + m_krbRealm = krbRealm; + } + else + throw new ServletException("Kerberos realm not specified"); + + // Get the HTTP service account password + + String srvPassword = args.getInitParameter("Password"); + if ( srvPassword != null && srvPassword.length() > 0) + { + // Set the HTTP service account password + + m_password = srvPassword; + } + else + throw new ServletException("HTTP service account password not specified"); + + // Get the login configuration entry name + + String loginEntry = args.getInitParameter("LoginEntry"); + + if ( loginEntry != null) + { + if ( loginEntry.length() > 0) + { + // Set the login configuration entry name to use + + m_loginEntryName = loginEntry; + } + else + throw new ServletException("Invalid login entry specified"); + } + + // Get the local host name + + String localName = null; + + try + { + localName = InetAddress.getLocalHost().getCanonicalHostName(); + } + catch ( UnknownHostException ex) + { + throw new ServletException( "Failed to get local host name"); + } + + // Create a login context for the HTTP server service + + try + { + // Login the HTTP server service + + m_loginContext = new LoginContext( m_loginEntryName, this); + m_loginContext.login(); + + // DEBUG + + if ( getLogger().isDebugEnabled()) + getLogger().debug( "HTTP Kerberos login successful"); + } + catch ( LoginException ex) + { + // Debug + + if ( getLogger().isErrorEnabled()) + getLogger().error("HTTP Kerberos web filter error", ex); + + throw new ServletException("Failed to login HTTP server service"); + } + + // Get the HTTP service account name from the subject + + Subject subj = m_loginContext.getSubject(); + Principal princ = subj.getPrincipals().iterator().next(); + + m_accountName = princ.getName(); + + // DEBUG + + if ( getLogger().isDebugEnabled()) + getLogger().debug("Logged on using principal " + m_accountName); + + // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback + + Vector mechTypes = new Vector(); + + mechTypes.add(OID.KERBEROS5); + mechTypes.add(OID.MSKERBEROS5); + + // Build the SPNEGO NegTokenInit blob + + try + { + // Build the mechListMIC principle + // + // Note: This field is not as specified + + String mecListMIC = null; + + StringBuilder mic = new StringBuilder(); + mic.append( localName); + mic.append("$@"); + mic.append( m_krbRealm); + + mecListMIC = mic.toString(); + + // Build the SPNEGO NegTokenInit that contains the authentication types that the HTTP server accepts + + NegTokenInit negTokenInit = new NegTokenInit(mechTypes, mecListMIC); + + // Encode the NegTokenInit blob + + m_negTokenInit = negTokenInit.encode(); + } + catch (IOException ex) + { + // Debug + + if ( getLogger().isErrorEnabled()) + getLogger().error("Error creating SPNEGO NegTokenInit blob", ex); + + throw new ServletException("Failed to create SPNEGO NegTokenInit blob"); + } + } + } + + /** + * 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); + + // If a filter up the chain has marked the request as not requiring auth then respect it + + if (req.getAttribute( NO_AUTH_REQUIRED) != null) + { + if ( getLogger().isDebugEnabled()) + getLogger().debug("Authentication not required (filter), chaining ..."); + + // Chain to the next filter + chain.doFilter(sreq, sresp); + return; + } + + // Check if there is an authorization header with an SPNEGO security blob + + String authHdr = req.getHeader("Authorization"); + boolean reqAuth = false; + + if ( authHdr != null) + { + // Check for a Kerberos/SPNEGO authorization header + + if ( authHdr.startsWith( "Negotiate")) + reqAuth = true; + else if ( authHdr.startsWith( "NTLM")) + { + if ( getLogger().isDebugEnabled()) + getLogger().debug("Received NTLM logon from client"); + + // Restart the authentication + + restartLoginChallenge(resp, httpSess); + + chain.doFilter(sreq, sresp); + return; + } + } + + // Check if the user is already authenticated + + SessionUser user = getSessionUser( httpSess); + + if ( user != null && reqAuth == false) + { + try + { + // Debug + + if ( getLogger().isDebugEnabled()) + getLogger().debug("User " + user.getUserName() + " validate ticket"); + + // Validate the user ticket + + m_authService.validate( user.getTicket()); + reqAuth = false; + + // Filter validate hook + onValidate( req, httpSess); + } + catch (AuthenticationException ex) + { + if ( getLogger().isErrorEnabled()) + getLogger().error("Failed to validate user " + user.getUserName(), ex); + + reqAuth = true; + } + } + + // If the user has been validated and we do not require re-authentication then continue to + // the next filter + + if ( reqAuth == false && user != null) + { + // Debug + + if ( getLogger().isDebugEnabled()) + getLogger().debug("Authentication not required (user), chaining ..."); + + // Chain to the next filter + + chain.doFilter(sreq, sresp); + return; + } + + // Check the authorization header + + if ( authHdr == null) { + + // If ticket based logons are allowed, check for a ticket parameter + + if ( allowsTicketLogons()) + { + // Check if a ticket parameter has been specified in the reuqest + + if ( checkForTicketParameter( req, httpSess)) + { + // Chain to the next filter + + chain.doFilter(sreq, sresp); + return; + } + } + + // Debug + + if ( getLogger().isDebugEnabled()) + getLogger().debug("New Kerberos auth request from " + req.getRemoteHost() + " (" + + req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); + + // Send back a request for SPNEGO authentication + + restartLoginChallenge( resp, httpSess); + } + else + { + // Decode the received SPNEGO blob and validate + + final byte[] spnegoByts = Base64.decodeBase64( authHdr.substring(10).getBytes()); + + // Check if the client sent an NTLMSSP blob + + if ( isNTLMSSPBlob( spnegoByts, 0)) + { + if ( getLogger().isDebugEnabled()) + getLogger().debug( "Client sent an NTLMSSP security blob"); + + // Restart the authentication + + restartLoginChallenge(resp, httpSess); + return; + } + + // Check the received SPNEGO token type + + int tokType = -1; + + try + { + tokType = SPNEGO.checkTokenType( spnegoByts, 0, spnegoByts.length); + } + catch ( IOException ex) + { + } + + // Check for a NegTokenInit blob + + if ( tokType == SPNEGO.NegTokenInit) + { + // Parse the SPNEGO security blob to get the Kerberos ticket + + NegTokenInit negToken = new NegTokenInit(); + + try + { + // Decode the security blob + + negToken.decode( spnegoByts, 0, spnegoByts.length); + + // Determine the authentication mechanism the client is using and logon + + String oidStr = null; + if ( negToken.numberOfOids() > 0) + oidStr = negToken.getOidAt( 0).toString(); + + if ( oidStr != null && (oidStr.equals( OID.ID_MSKERBEROS5) || oidStr.equals(OID.ID_KERBEROS5))) + { + // Kerberos logon + + if ( doKerberosLogon( negToken, req, resp, httpSess) != null) + { + // Allow the user to access the requested page + + chain.doFilter( req, resp); + } + else + { + // Send back a request for SPNEGO authentication + + restartLoginChallenge( resp, httpSess); + } + } + } + catch ( IOException ex) + { + // Log the error + + if ( getLogger().isDebugEnabled()) + getLogger().debug(ex); + } + } + else + { + // Unknown SPNEGO token type + + if ( getLogger().isDebugEnabled()) + getLogger().debug( "Unknown SPNEGO token type"); + + // Send back a request for SPNEGO authentication + + restartLoginChallenge( resp, httpSess); + } + } + } + + /** + * JAAS callback handler + * + * @param callbacks Callback[] + * @exception IOException + * @exception UnsupportedCallbackException + */ + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + // Process the callback list + + for (int i = 0; i < callbacks.length; i++) + { + // Request for user name + + if (callbacks[i] instanceof NameCallback) + { + NameCallback cb = (NameCallback) callbacks[i]; + cb.setName(m_accountName); + } + + // Request for password + else if (callbacks[i] instanceof PasswordCallback) + { + PasswordCallback cb = (PasswordCallback) callbacks[i]; + cb.setPassword(m_password.toCharArray()); + } + + // Request for realm + + else if (callbacks[i] instanceof RealmCallback) + { + RealmCallback cb = (RealmCallback) callbacks[i]; + cb.setText(m_krbRealm); + } + else + { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + + /** + * Perform a Kerberos login and return an SPNEGO response + * + * @param negToken NegTokenInit + * @param req HttpServletRequest + * @param resp HttpServletResponse + * @param httpSess HttpSession + * @return NegTokenTarg + */ + private final NegTokenTarg doKerberosLogon( NegTokenInit negToken, HttpServletRequest req, HttpServletResponse resp, HttpSession httpSess) + { + // Authenticate the user + + KerberosDetails krbDetails = null; + NegTokenTarg negTokenTarg = null; + + try + { + // Run the session setup as a privileged action + + SessionSetupPrivilegedAction sessSetupAction = new SessionSetupPrivilegedAction( m_accountName, negToken.getMechtoken()); + Object result = Subject.doAs( m_loginContext.getSubject(), sessSetupAction); + + if ( result != null) + { + // Access the Kerberos response + + krbDetails = (KerberosDetails) result; + + // Create the NegTokenTarg response blob + + negTokenTarg = new NegTokenTarg( SPNEGO.AcceptCompleted, OID.KERBEROS5, krbDetails.getResponseToken()); + + // Check if the user has been authenticated, if so then setup the user environment + + if ( negTokenTarg != null) + { + + // Create the user authentication context + + SessionUser user = createUserEnvironment( httpSess, krbDetails.getUserName()); + + // Store the user + + httpSess.setAttribute(AUTHENTICATION_USER, user); + + // Debug + + if ( getLogger().isDebugEnabled()) + getLogger().debug("User " + user.getUserName() + " logged on via Kerberos"); + } + } + else + { + // Debug + + if ( getLogger().isDebugEnabled()) + getLogger().debug( "No SPNEGO response, Kerberos logon failed"); + } + } + catch (Exception ex) + { + // Log the error + + if ( getLogger().isDebugEnabled()) + getLogger().debug("Kerberos logon error", ex); + } + + // Return the response SPNEGO blob + + return negTokenTarg; + } + + /** + * Restart the Kerberos logon process + * + * @param resp HttpServletResponse + * @param httpSess HttpSession + * @throws IOException + */ + protected void restartLoginChallenge(HttpServletResponse resp, HttpSession session) throws IOException + { + // Force the logon to start again + + resp.setHeader("WWW-Authenticate", "Negotiate"); + resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + resp.flushBuffer(); + } +} diff --git a/source/java/org/alfresco/repo/webdav/auth/BaseNTLMAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/BaseNTLMAuthenticationFilter.java index d4963c192a..93b42534c9 100644 --- a/source/java/org/alfresco/repo/webdav/auth/BaseNTLMAuthenticationFilter.java +++ b/source/java/org/alfresco/repo/webdav/auth/BaseNTLMAuthenticationFilter.java @@ -25,19 +25,18 @@ package org.alfresco.repo.webdav.auth; import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Random; -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; @@ -46,40 +45,29 @@ import javax.transaction.UserTransaction; import net.sf.acegisecurity.BadCredentialsException; -import org.alfresco.filesys.ServerConfigurationBean; import org.alfresco.jlan.server.auth.PasswordEncryptor; import org.alfresco.jlan.server.auth.ntlm.NTLM; import org.alfresco.jlan.server.auth.ntlm.NTLMLogonDetails; +import org.alfresco.jlan.server.auth.ntlm.NTLMMessage; import org.alfresco.jlan.server.auth.ntlm.NTLMv2Blob; import org.alfresco.jlan.server.auth.ntlm.TargetInfo; import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage; import org.alfresco.jlan.server.auth.ntlm.Type2NTLMMessage; import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage; -import org.alfresco.jlan.server.auth.passthru.DomainMapping; -import org.alfresco.jlan.server.config.SecurityConfigSection; import org.alfresco.jlan.util.DataPacker; -import org.alfresco.jlan.util.IPAddress; import org.alfresco.repo.SessionUser; -import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.MD4PasswordEncoder; import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl; import org.alfresco.repo.security.authentication.NTLMMode; import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; -import org.alfresco.service.ServiceRegistry; -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.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; /** * Base class with common code and initialisation for NTLM authentication filters. */ -public abstract class BaseNTLMAuthenticationFilter implements Filter +public abstract class BaseNTLMAuthenticationFilter extends BaseSSOAuthenticationFilter { // NTLM authentication session object names public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess"; @@ -109,22 +97,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter // NTLM flags to send to the client with the allowed logon types private int m_ntlmFlags; - // Servlet context, required to get authentication service - protected ServletContext m_context; - - // File server configuration - private ServerConfigurationBean m_srvConfig; - - // Security configuration section, for domain mappings - private SecurityConfigSection m_secConfig; - - // Various services required by NTLM authenticator - protected AuthenticationService m_authService; - protected AuthenticationComponent m_authComponent; - protected PersonService m_personService; - protected NodeService m_nodeService; - protected TransactionService m_transactionService; - // Password encryptor private PasswordEncryptor m_encryptor = new PasswordEncryptor(); @@ -134,9 +106,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter // MD4 hash decoder private MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl(); - // Local server name, from either the file servers config or DNS host name - protected String m_srvName; - // Allow guest access private boolean m_allowGuest = false; @@ -155,88 +124,20 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter */ public void init(FilterConfig args) throws ServletException { - // Save the servlet context, needed to get hold of the authentication service - m_context = args.getServletContext(); - - // Setup the authentication context - WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); - - ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - m_nodeService = serviceRegistry.getNodeService(); - m_transactionService = serviceRegistry.getTransactionService(); - m_authService = serviceRegistry.getAuthenticationService(); - - m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); - m_personService = (PersonService) ctx.getBean("personService"); - - m_srvConfig = (ServerConfigurationBean) ctx.getBean(ServerConfigurationBean.SERVER_CONFIGURATION); - - // Check that the authentication component supports the required mode + // Call the base SSO filter initialization + + super.init( args); + + // Check that the authentication component supports the required mode + if (m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) { throw new ServletException("Required authentication mode not available"); } - // Get the local server name, try the file server config first - if (m_srvConfig != null) - { - m_srvName = m_srvConfig.getServerName(); - - if (m_srvName != null) - { - try - { - InetAddress resolved = InetAddress.getByName(m_srvName); - if (resolved == null) - { - // failed to resolve the configured name - m_srvName = m_srvConfig.getLocalServerName(true); - } - } - catch (UnknownHostException ex) - { - if (getLogger().isErrorEnabled()) - getLogger().error("NTLM filter, error getting resolving host name", ex); - } - } - else - { - m_srvName = m_srvConfig.getLocalServerName(true); - } - - // Find the security configuration section - m_secConfig = (SecurityConfigSection)m_srvConfig.getConfigSection(SecurityConfigSection.SectionName); - } - else - { - // Get the host name - try - { - // Get the local host name - m_srvName = InetAddress.getLocalHost().getHostName(); - - // Strip any domain name - int pos = m_srvName.indexOf("."); - if (pos != -1) - { - m_srvName = m_srvName.substring(0, pos - 1); - } - } - catch (UnknownHostException ex) - { - if (getLogger().isErrorEnabled()) - getLogger().error("NTLM filter, error getting local host name", ex); - } - } - - // Check if the server name is valid - if (m_srvName == null || m_srvName.length() == 0) - { - throw new ServletException("Failed to get local server name"); - } - // Check if guest access is to be allowed + String guestAccess = args.getInitParameter("AllowGuest"); if (guestAccess != null) { @@ -247,6 +148,7 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter } // Check if unknown users should be mapped to guest access + String mapUnknownToGuest = args.getInitParameter("MapUnknownUserToGuest"); if (mapUnknownToGuest != null) { @@ -258,6 +160,7 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter // Set the NTLM flags depending on the authentication component supporting MD4 passwords, // or is using passthru auth + if (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER && m_disableNTLMv2 == false) { // Allow the client to use an NTLMv2 logon @@ -272,6 +175,179 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter } } + /** + * 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); + + // If a filter up the chain has marked the request as not requiring auth then respect it + + if (req.getAttribute( NO_AUTH_REQUIRED) != null) + { + if ( getLogger().isDebugEnabled()) + getLogger().debug("Authentication not required (filter), chaining ..."); + + // Chain to the next filter + chain.doFilter(sreq, sresp); + return; + } + + // Check if there is an authorization header with an NTLM security blob + String authHdr = req.getHeader(AUTHORIZATION); + boolean reqAuth = false; + + // Check if an NTLM authorization header was received + + if ( authHdr != null) + { + // Check for an NTLM authorization header + + if ( authHdr.startsWith(AUTH_NTLM)) + reqAuth = true; + else if ( authHdr.startsWith( "Negotiate")) + { + if ( getLogger().isDebugEnabled()) + getLogger().debug("Received 'Negotiate' from client, may be SPNEGO/Kerberos logon"); + + // Restart the authentication + + restartLoginChallenge(resp, httpSess); + return; + } + } + + // Check if the user is already authenticated + SessionUser user = getSessionUser( httpSess); + + if (user != null && reqAuth == false) + { + try + { + if (getLogger().isDebugEnabled()) + getLogger().debug("User " + user.getUserName() + " validate ticket"); + + // Validate the user ticket + m_authService.validate(user.getTicket()); + reqAuth = false; + + // Filter validate hook + onValidate( req, httpSess); + } + catch (AuthenticationException ex) + { + if (getLogger().isErrorEnabled()) + getLogger().error("Failed to validate user " + user.getUserName(), ex); + + reqAuth = true; + } + } + + // If the user has been validated and we do not require re-authentication then continue to + // the next filter + if (reqAuth == false && user != null) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Authentication not required (user), chaining ..."); + + // Chain to the next filter + chain.doFilter(sreq, sresp); + return; + } + + // Check if the login page is being accessed, do not intercept the login page + if (hasLoginPage() && req.getRequestURI().endsWith(getLoginPage()) == true) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Login page requested, chaining ..."); + + // Chain to the next filter + chain.doFilter( sreq, sresp); + return; + } + + // Check if the browser is Opera, if so then display the login page as Opera does not + // support NTLM and displays an error page if a request to use NTLM is sent to it + String userAgent = req.getHeader("user-agent"); + if (userAgent != null && userAgent.indexOf("Opera ") != -1) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Opera detected, redirecting to login page"); + + // If there is no login page configured (WebDAV) then just keep requesting the user details from the client + + if ( hasLoginPage()) + redirectToLoginPage(req, resp); + else + restartLoginChallenge(resp, httpSess); + return; + } + + // Check the authorization header + if (authHdr == null) + { + // Check for a ticket based logon, if enabled + + if ( allowsTicketLogons()) + { + // Check if the request includes an authentication ticket + + if ( checkForTicketParameter(req, httpSess)) { + + // Authentication was bypassed using a ticket parameter + + chain.doFilter(sreq, sresp); + return; + } + } + + // DEBUG + + if (getLogger().isDebugEnabled()) + getLogger().debug("New NTLM auth request from " + req.getRemoteHost() + " (" + + req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); + + // Send back a request for NTLM authentication + restartLoginChallenge(resp, httpSess); + } + else + { + // Decode the received NTLM blob and validate + final byte[] ntlmByts = Base64.decodeBase64(authHdr.substring(5).getBytes()); + int ntlmTyp = NTLMMessage.isNTLMType(ntlmByts); + if (ntlmTyp == NTLM.Type1) + { + // Process the type 1 NTLM message + Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts); + processType1(type1Msg, req, resp, httpSess); + } + else if (ntlmTyp == NTLM.Type3) + { + // Process the type 3 NTLM message + Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts); + processType3(type3Msg, req, resp, httpSess, chain); + } + else + { + if (getLogger().isDebugEnabled()) + getLogger().debug("NTLM blob not handled, redirecting to login page."); + + redirectToLoginPage(req, resp); + } + } + } + /** * Delete the servlet filter */ @@ -293,8 +369,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter { Log logger = getLogger(); - if (logger.isDebugEnabled()) - logger.debug("Received type1 " + type1Msg); + if (getLogger().isDebugEnabled()) + getLogger().debug("Received type1 " + type1Msg); // Get the existing NTLM details NTLMLogonDetails ntlmDetails = null; @@ -314,8 +390,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter byte[] type2Bytes = cachedType2.getBytes(); String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes)); - if (logger.isDebugEnabled()) - logger.debug("Sending cached NTLM type2 to client - " + cachedType2); + if (getLogger().isDebugEnabled()) + getLogger().debug("Sending cached NTLM type2 to client - " + cachedType2); // Send back a request for NTLM authentication res.setHeader(WWW_AUTHENTICATE, ntlmBlob); @@ -347,8 +423,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter domain = mapClientAddressToDomain(req.getRemoteAddr()); } - if (logger.isDebugEnabled()) - logger.debug("Client domain " + domain); + if (getLogger().isDebugEnabled()) + getLogger().debug("Client domain " + domain); // Create an authentication token for the new logon authToken = new NTLMPassthruToken(domain); @@ -380,8 +456,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); - if (logger.isDebugEnabled()) - logger.debug("Sending NTLM type2 to client - " + type2Msg); + if (getLogger().isDebugEnabled()) + getLogger().debug("Sending NTLM type2 to client - " + type2Msg); // Send back a request for NTLM authentication byte[] type2Bytes = type2Msg.getBytes(); @@ -439,15 +515,7 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter if (ntlmPwd != null) { - if (ntlmPwd.length == cachedPwd.length) - { - authenticated = true; - for (int i = 0; i < ntlmPwd.length; i++) - { - if (ntlmPwd[i] != cachedPwd[i]) - authenticated = false; - } - } + authenticated = Arrays.equals( cachedPwd, ntlmPwd); } if (logger.isDebugEnabled()) @@ -487,8 +555,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter // Indicate that the user has been authenticated authenticated = true; - if (logger.isDebugEnabled()) - logger.debug("Guest logon"); + if (getLogger().isDebugEnabled()) + getLogger().debug("Guest logon"); } else { @@ -635,46 +703,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter } } - /** - * Callback to get the specific impl of the Session User for a filter - * - * @return User from the session - */ - protected abstract SessionUser getSessionUser(HttpSession session); - - /** - * Callback to create the User environment as appropriate for a filter impl - * - * @param session - * @param userName - * - * @return SessionUser impl - * - * @throws IOException - * @throws ServletException - */ - protected abstract SessionUser createUserEnvironment(HttpSession session, String userName) - throws IOException, ServletException; - - /** - * Callback executed on successful ticket validation during Type3 Message processing - */ - protected abstract void onValidate(HttpServletRequest req, HttpSession session); - - /** - * Callback executed on failed authentication of a user ticket during Type3 Message processing - */ - protected abstract void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session) - throws IOException; - - /** - * Callback executed on completion of NTLM login - * - * @return true to continue filter chaining, false otherwise - */ - protected abstract boolean onLoginComplete(HttpServletRequest req, HttpServletResponse res) - throws IOException; - /** * Validate the MD4 hash against local password * @@ -1030,6 +1058,8 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter } /** + * Restart the NTLM logon process + * * @param resp * @param httpSess * @throws IOException @@ -1046,50 +1076,6 @@ public abstract class BaseNTLMAuthenticationFilter implements Filter res.flushBuffer(); } - /** - * Map a client IP address to a domain - * - * @param clientIP String - * @return String - */ - protected final String mapClientAddressToDomain(String clientIP) - { - // Check if there are any domain mappings - if (m_secConfig != null && m_secConfig.hasDomainMappings() == false) - { - return null; - } - - if (m_secConfig != null) - { - // convert the client IP address to an integer value - int clientAddr = IPAddress.parseNumericAddress(clientIP); - for (DomainMapping domainMap : m_secConfig.getDomainMappings()) - { - if (domainMap.isMemberOfDomain(clientAddr)) - { - if (getLogger().isDebugEnabled()) - getLogger().debug("Mapped client IP " + clientIP + " to domain " + domainMap.getDomain()); - - return domainMap.getDomain(); - } - } - } - - if (getLogger().isDebugEnabled()) - getLogger().debug("Failed to map client IP " + clientIP + " to a domain"); - - // No domain mapping for the client address - return null; - } - - /** - * Return the logger - * - * @return Log - */ - protected abstract Log getLogger(); - /** * Disable NTLMv2 support, must be called from the implementation constructor */ diff --git a/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java new file mode 100644 index 0000000000..d27cd0be03 --- /dev/null +++ b/source/java/org/alfresco/repo/webdav/auth/BaseSSOAuthenticationFilter.java @@ -0,0 +1,580 @@ +/* + * 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.net.InetAddress; +import java.net.UnknownHostException; + +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.transaction.UserTransaction; + +import org.alfresco.filesys.ServerConfigurationBean; +import org.alfresco.jlan.server.auth.ntlm.NTLM; +import org.alfresco.jlan.server.auth.passthru.DomainMapping; +import org.alfresco.jlan.server.config.SecurityConfigSection; +import org.alfresco.jlan.util.IPAddress; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.SessionUser; +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.apache.commons.logging.Log; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Base class with common code and initialisation for single signon authentication filters. + * + * @author gkspencer + * @author kroast + */ +public abstract class BaseSSOAuthenticationFilter implements Filter +{ + // Constants + // + // Session value names + // + // Note: These values are copied from the AuthenticationHelper and LoginBean classes to avoid project dependencies + + protected static final String AUTHENTICATION_USER = "_alfAuthTicket"; + protected static final String LOGIN_EXTERNAL_AUTH = "_alfExternalAuth"; + + // Request level marker to indicate that authentication should not be processed + // + // Note: copied from the AbstractAuthenticationFilter to avoid project dependencies + + protected static final String NO_AUTH_REQUIRED = "alfNoAuthRequired"; + + // Allow an authenitcation ticket to be passed as part of a request to bypass authentication + + private static final String ARG_TICKET = "ticket"; + + // Servlet context, required to get authentication service + + protected ServletContext m_context; + + // File server configuration + + private ServerConfigurationBean m_srvConfig; + + // Security configuration section, for domain mappings + + private SecurityConfigSection m_secConfig; + + // Various services required by NTLM authenticator + + protected AuthenticationService m_authService; + protected AuthenticationComponent m_authComponent; + protected PersonService m_personService; + protected NodeService m_nodeService; + protected TransactionService m_transactionService; + + // Local server name, from either the file servers config or DNS host name + + protected String m_srvName; + + // Login page relative address, if null then login will loop until a valid login is received + + private String m_loginPage; + + // Indicate whether ticket based logons are supported + + private boolean m_ticketLogons; + + /** + * Initialize the filter + * + * @param args FilterConfig + * + * @exception ServletException + */ + public void init(FilterConfig args) throws ServletException + { + // Save the servlet context, needed to get hold of the authentication service + + m_context = args.getServletContext(); + + // Setup the authentication context + + WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); + + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + m_nodeService = serviceRegistry.getNodeService(); + m_transactionService = serviceRegistry.getTransactionService(); + m_authService = serviceRegistry.getAuthenticationService(); + + m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); + m_personService = (PersonService) ctx.getBean("personService"); + + m_srvConfig = (ServerConfigurationBean) ctx.getBean(ServerConfigurationBean.SERVER_CONFIGURATION); + + // Get the local server name, try the file server config first + + if (m_srvConfig != null) + { + m_srvName = m_srvConfig.getServerName(); + + if (m_srvName != null) + { + try + { + InetAddress resolved = InetAddress.getByName(m_srvName); + if (resolved == null) + { + // Failed to resolve the configured name + + m_srvName = m_srvConfig.getLocalServerName(true); + } + } + catch (UnknownHostException ex) + { + if (getLogger().isErrorEnabled()) + getLogger().error("NTLM filter, error resolving CIFS host name", ex); + } + } + + // If we still do not have a name use the DNS name of the server, with the domain part removed + + if ( m_srvName == null) + { + m_srvName = m_srvConfig.getLocalServerName(true); + } + + // Find the security configuration section + + m_secConfig = (SecurityConfigSection)m_srvConfig.getConfigSection(SecurityConfigSection.SectionName); + } + else + { + // Get the host name + + try + { + // Get the local host name + + m_srvName = InetAddress.getLocalHost().getHostName(); + + // Strip any domain name + + int pos = m_srvName.indexOf("."); + if (pos != -1) + { + m_srvName = m_srvName.substring(0, pos - 1); + } + } + catch (UnknownHostException ex) + { + if (getLogger().isErrorEnabled()) + getLogger().error("NTLM filter, error getting local host name", ex); + } + } + + // Check if the server name is valid + + if (m_srvName == null || m_srvName.length() == 0) + { + throw new ServletException("Failed to get local server name"); + } + } + + /** + * Delete the servlet filter + */ + public void destroy() + { + } + + /** + * Create the user object that will be stored in the session + * + * @param userName String + * @param ticket String + * @param personNode NodeRef + * @param homeSpace String + * @return SessionUser + */ + protected abstract SessionUser createUserObject( String userName, String ticket, NodeRef personNode, String homeSpace); + + /** + * Callback to get the specific impl of the Session User for a filter + * + * @return User from the session + */ + protected SessionUser getSessionUser(HttpSession session) + { + return (SessionUser)session.getAttribute( AUTHENTICATION_USER); + } + + /** + * Callback to create the User environment as appropriate for a filter impl + * + * @param session HttpSession + * @param userName String + * @return SessionUser + * @throws IOException + * @throws ServletException + */ + protected SessionUser createUserEnvironment(HttpSession session, String userName) + throws IOException, ServletException + { + SessionUser user = null; + + UserTransaction tx = m_transactionService.getUserTransaction(); + + try + { + tx.begin(); + + // Setup User object and Home space ID etc. + + NodeRef personNodeRef = m_personService.getPerson(userName); + + // Use the system user context to do the user lookup + + m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); + + // User name should match the uid in the person entry found + + m_authComponent.setSystemUserAsCurrentUser(); + userName = (String) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + m_authComponent.setCurrentUser(userName); + String currentTicket = m_authService.getCurrentTicket(); + + NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); + + // Create the user object to be stored in the session + + user = createUserObject( userName, currentTicket, personNodeRef, homeSpaceRef.getId()); + + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Exception err) + { + getLogger().error("Failed to rollback transaction", err); + } + 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 on the session + + session.setAttribute( AUTHENTICATION_USER, user); + session.setAttribute( LOGIN_EXTERNAL_AUTH, Boolean.TRUE); + + return user; + } + + /** + * Callback executed on successful ticket validation during Type3 Message processing + * + * @param req HttpServletReqeust + * @param session HttpSession + */ + protected void onValidate(HttpServletRequest req, HttpSession session) + { + } + + /** + * Callback executed on failed authentication of a user ticket during Type3 Message processing + * + * @param req HttpServletRequest + * @param res HttpServletResponse + * @param session HttpSession + */ + protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session) + throws IOException + { + } + + /** + * Callback executed on completion of NTLM login + * + * @param req HttpServletRequest + * @param res HttpServletResponse + * @return true to continue filter chaining, false otherwise + */ + protected boolean onLoginComplete(HttpServletRequest req, HttpServletResponse res) + throws IOException + { + return true; + } + + /** + * Map a client IP address to a domain + * + * @param clientIP String + * @return String + */ + protected final String mapClientAddressToDomain(String clientIP) + { + // Check if there are any domain mappings + + if (m_secConfig != null && m_secConfig.hasDomainMappings() == false) + { + return null; + } + + if (m_secConfig != null) + { + // Convert the client IP address to an integer value + + int clientAddr = IPAddress.parseNumericAddress(clientIP); + for (DomainMapping domainMap : m_secConfig.getDomainMappings()) + { + if (domainMap.isMemberOfDomain(clientAddr)) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Mapped client IP " + clientIP + " to domain " + domainMap.getDomain()); + + return domainMap.getDomain(); + } + } + } + + if (getLogger().isDebugEnabled()) + getLogger().debug("Failed to map client IP " + clientIP + " to a domain"); + + // No domain mapping for the client address + + return null; + } + + /** + * Check if the request has specified a ticket parameter to bypass the standard authentication + * + * @param req HttpServletRequest + * @param sess HttpSession + * @return boolean + */ + protected boolean checkForTicketParameter( HttpServletRequest req, HttpSession sess) + { + // Check if the request includes an authentication ticket + + boolean ticketValid = false; + String ticket = req.getParameter(ARG_TICKET); + + if (ticket != null && ticket.length() != 0) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Logon via ticket from " + req.getRemoteHost() + " (" + + req.getRemoteAddr() + ":" + req.getRemotePort() + ")" + " ticket=" + ticket); + + UserTransaction tx = null; + try + { + // Validate the ticket + + m_authService.validate(ticket); + + SessionUser user = getSessionUser( sess); + + if ( user == null) + { + // Start a transaction + + tx = m_transactionService.getUserTransaction(); + tx.begin(); + + // Need to create the User instance if not already available + + String currentUsername = m_authService.getCurrentUserName(); + + NodeRef personRef = m_personService.getPerson(currentUsername); + user = createUserObject( currentUsername, m_authService.getCurrentTicket(), personRef, null); + + tx.commit(); + tx = null; + + // Store the User object in the Session - the authentication servlet will then proceed + + req.getSession().setAttribute(AUTHENTICATION_USER, user); + } + + // Indicate the ticket parameter was specified, and valid + + ticketValid = true; + } + catch (AuthenticationException authErr) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Failed to authenticate user ticket: " + authErr.getMessage(), authErr); + } + catch (Throwable e) + { + if (getLogger().isDebugEnabled()) + getLogger().debug("Error during ticket validation and user creation: " + e.getMessage(), e); + } + finally + { + try + { + if (tx != null) + { + tx.rollback(); + } + } + catch (Exception tex) + { + } + } + } + + // Return the ticket parameter status + + return ticketValid; + } + + /** + * Redirect to the login page + * + * @param req HttpServletRequest + * @param req HttpServletResponse + * @exception IOException + */ + protected void redirectToLoginPage(HttpServletRequest req, HttpServletResponse res) + throws IOException + { + if ( hasLoginPage()) + res.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); + } + + /** + * Return the logger + * + * @return Log + */ + protected abstract Log getLogger(); + + /** + * Determine if the login page is available + * + * @return boolean + */ + protected final boolean hasLoginPage() + { + return m_loginPage != null ? true : false; + } + + /** + * Return the login page address + * + * @return String + */ + protected final String getLoginPage() + { + return m_loginPage; + } + + /** + * Set the login page address + * + * @param loginPage String + */ + protected final void setLoginPage( String loginPage) + { + m_loginPage = loginPage; + } + + /** + * Check if ticket based logons are allowed + * + * @return boolean + */ + protected final boolean allowsTicketLogons() + { + return m_ticketLogons; + } + + /** + * Set the ticket based logons allowed flag + * + * @param ticketsAllowed boolean + */ + protected final void setTicketLogons( boolean ticketsAllowed) + { + m_ticketLogons = ticketsAllowed; + } + + /** + * Check if a security blob starts with the NTLMSSP signature + * + * @param byts byte[] + * @param offset int + * @return boolean + */ + protected final boolean isNTLMSSPBlob( byte[] byts, int offset) + { + // Check if the blob has the NTLMSSP signature + + boolean isNTLMSSP = false; + + if (( byts.length - offset) >= NTLM.Signature.length) { + + // Check for the NTLMSSP signature + + int idx = 0; + while ( idx < NTLM.Signature.length && byts[offset + idx] == NTLM.Signature[ idx]) + idx++; + + if ( idx == NTLM.Signature.length) + isNTLMSSP = true; + } + + return isNTLMSSP; + } +} diff --git a/source/java/org/alfresco/repo/webdav/auth/KerberosAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/KerberosAuthenticationFilter.java index 096be4df90..60646fdcea 100644 --- a/source/java/org/alfresco/repo/webdav/auth/KerberosAuthenticationFilter.java +++ b/source/java/org/alfresco/repo/webdav/auth/KerberosAuthenticationFilter.java @@ -25,135 +25,29 @@ package org.alfresco.repo.webdav.auth; import java.io.IOException; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.Principal; -import java.util.Vector; -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.security.sasl.RealmCallback; -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.filesys.ServerConfigurationBean; -import org.alfresco.jlan.server.auth.kerberos.KerberosDetails; -import org.alfresco.jlan.server.auth.kerberos.SessionSetupPrivilegedAction; -import org.alfresco.jlan.server.auth.spnego.NegTokenInit; -import org.alfresco.jlan.server.auth.spnego.NegTokenTarg; -import org.alfresco.jlan.server.auth.spnego.OID; -import org.alfresco.jlan.server.auth.spnego.SPNEGO; -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.AuthenticationUtil; -import org.alfresco.repo.security.authentication.NTLMMode; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.repo.SessionUser; 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.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.ietf.jgss.Oid; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.WebApplicationContextUtils; /** * WebDAV Kerberos Authentication Filter Class * * @author GKSpencer */ -public class KerberosAuthenticationFilter implements Filter, CallbackHandler +public class KerberosAuthenticationFilter extends BaseKerberosAuthenticationFilter { - // Constants - // - // Default login configuration entry name - - private static final String LoginConfigEntry = "AlfrescoHTTP"; - - // 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"; - // Debug logging private static Log logger = LogFactory.getLog(KerberosAuthenticationFilter.class); - - // Servlet context, required to get authentication service - - private ServletContext m_context; - - // File server configuration - - private ServerConfigurationBean m_srvConfig; - - // Various services required by the Kerberos authenticator - - private AuthenticationService m_authService; - private AuthenticationComponent m_authComponent; - private PersonService m_personService; - private NodeService m_nodeService; - private TransactionService m_transactionService; - - // Login page address - - private String m_loginPage; - // Local server name, from either the file servers config or DNS host name - - private String m_srvName; - - // Kerberos settings - // - // Account name and password for server ticket - // - // The account name must be built from the HTTP server name, in the format :- - // - // HTTP/@ - - private String m_accountName; - private String m_password; - - // Kerberos realm and KDC address - - private String m_krbRealm; - private String m_krbKDC; - - // Login configuration entry name - - private String m_loginEntryName = LoginConfigEntry; - - // Server login context - - private LoginContext m_loginContext; - - // SPNEGO NegTokenInit blob, sent to the client in the SMB negotiate response - - private byte[] m_negTokenInit; - /** * Initialize the filter * @@ -162,698 +56,43 @@ public class KerberosAuthenticationFilter implements Filter, CallbackHandler */ public void init(FilterConfig args) throws ServletException { - // Save the servlet context, needed to get hold of the authentication service - - m_context = args.getServletContext(); - - // Setup the authentication context - - WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); - - ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - m_nodeService = serviceRegistry.getNodeService(); - m_transactionService = serviceRegistry.getTransactionService(); - - m_authService = (AuthenticationService) ctx.getBean("AuthenticationService"); - m_authComponent = (AuthenticationComponent) ctx.getBean("AuthenticationComponent"); - m_personService = (PersonService) ctx.getBean("personService"); - - m_srvConfig = (ServerConfigurationBean) ctx.getBean(ServerConfigurationBean.SERVER_CONFIGURATION); - - // Check that the authentication component supports the required mode - - if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && - m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) - { - throw new ServletException("Required authentication mode not available"); - } - - // Get the local server name, try the file server config first - - if ( m_srvConfig != null) - { - m_srvName = m_srvConfig.getServerName(); - - if ( m_srvName == null) - { - // CIFS server may not be running so the local server name has not been set, generate - // a server name - - m_srvName = m_srvConfig.getLocalServerName(true) + "_A"; - } - } - else - { - // Get the host name - - try - { - // Get the local host name - - m_srvName = InetAddress.getLocalHost().getHostName(); - - // Strip any domain name - - int pos = m_srvName.indexOf("."); - if ( pos != -1) - m_srvName = m_srvName.substring(0, pos - 1); - } - catch (UnknownHostException ex) - { - // Log the error - - if ( logger.isErrorEnabled()) - logger.error("Kerberos filter, error getting local host name", ex); - } - - } - - // Check if the server name is valid - - if ( m_srvName == null || m_srvName.length() == 0) - throw new ServletException("Failed to get local server name"); - - // Check if Kerberos is enabled, get the Kerberos KDC address - - String kdcAddress = args.getInitParameter("KDC"); - - if (kdcAddress != null && kdcAddress.length() > 0) - { - // Set the Kerberos KDC address - - m_krbKDC = kdcAddress; - - // Get the Kerberos realm - - String krbRealm = args.getInitParameter("Realm"); - if ( krbRealm != null && krbRealm.length() > 0) - { - // Set the Kerberos realm - - m_krbRealm = krbRealm; - } - else - throw new ServletException("Kerberos realm not specified"); - - // Get the HTTP service account password - - String srvPassword = args.getInitParameter("Password"); - if ( srvPassword != null && srvPassword.length() > 0) - { - // Set the HTTP service account password - - m_password = srvPassword; - } - else - throw new ServletException("HTTP service account password not specified"); - - // Get the login configuration entry name - - String loginEntry = args.getInitParameter("LoginEntry"); - - if ( loginEntry != null) - { - if ( loginEntry.length() > 0) - { - // Set the login configuration entry name to use - - m_loginEntryName = loginEntry; - } - else - throw new ServletException("Invalid login entry specified"); - } - - // Get the local host name - - String localName = null; - - try - { - localName = InetAddress.getLocalHost().getCanonicalHostName(); - } - catch ( UnknownHostException ex) - { - throw new ServletException( "Failed to get local host name"); - } - - // Create a login context for the HTTP server service - - try - { - // Login the HTTP server service - - m_loginContext = new LoginContext( m_loginEntryName, this); - m_loginContext.login(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug( "HTTP Kerberos login successful"); - } - catch ( LoginException ex) - { - // Debug - - if ( logger.isErrorEnabled()) - logger.error("HTTP Kerberos web filter error", ex); - - throw new ServletException("Failed to login HTTP server service"); - } - - // Get the HTTP service account name from the subject - - Subject subj = m_loginContext.getSubject(); - Principal princ = subj.getPrincipals().iterator().next(); - - m_accountName = princ.getName(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Logged on using principal " + m_accountName); - - // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback - - Vector mechTypes = new Vector(); - - mechTypes.add(OID.KERBEROS5); - mechTypes.add(OID.MSKERBEROS5); - - // Build the SPNEGO NegTokenInit blob - - try - { - // Build the mechListMIC principle - // - // Note: This field is not as specified - - String mecListMIC = null; - - StringBuilder mic = new StringBuilder(); - mic.append( localName); - mic.append("$@"); - mic.append( m_krbRealm); - - mecListMIC = mic.toString(); - - // Build the SPNEGO NegTokenInit that contains the authentication types that the HTTP server accepts - - NegTokenInit negTokenInit = new NegTokenInit(mechTypes, mecListMIC); - - // Encode the NegTokenInit blob - - m_negTokenInit = negTokenInit.encode(); - } - catch (IOException ex) - { - // Debug - - if ( logger.isErrorEnabled()) - logger.error("Error creating SPNEGO NegTokenInit blob", ex); - - throw new ServletException("Failed to create SPNEGO NegTokenInit blob"); - } - } + // Call the base Kerberos filter initialization + + super.init( args); + + // Enable ticket based logons + + setTicketLogons( true); } - /** - * Run the filter - * - * @param sreq ServletRequest - * @param sresp ServletResponse - * @param chain FilterChain - * @exception IOException - * @exception ServletException + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#createUserObject(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + @Override + protected SessionUser createUserObject(String userName, String ticket, NodeRef personNode, String homeSpace) { + + // Create a WebDAV user object + + return new WebDAVUser( userName, ticket, personNode); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession) */ - 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 if there is an authorization header with an SPNEGO security blob - - String authHdr = req.getHeader("Authorization"); - boolean reqAuth = false; - - if ( authHdr != null && authHdr.startsWith("Negotiate")) - reqAuth = true; - - // Check if the user is already authenticated - - WebDAVUser user = (WebDAVUser) httpSess.getAttribute( AUTHENTICATION_USER); - - if ( user != null && reqAuth == false) - { - try - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("User " + user.getUserName() + " validate ticket"); - - // Validate the user ticket - - m_authService.validate( user.getTicket()); - reqAuth = false; - } - catch (AuthenticationException ex) - { - if ( logger.isErrorEnabled()) - logger.error("Failed to validate user " + user.getUserName(), ex); - - reqAuth = true; - } - } - - // If the user has been validated and we do not require re-authentication then continue to - // the next filter - - if ( reqAuth == false && user != null) - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Authentication not required, chaining ..."); - - // Chain to the next filter - - chain.doFilter(sreq, sresp); - return; - } - - // Check the authorization header - - if ( authHdr == null) { - - // 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 - - req.getSession().setAttribute( AUTHENTICATION_USER, user); - - // Chain to the next filter - - chain.doFilter(sreq, sresp); - return; - } - catch (AuthenticationException authErr) - { - // Clear the user object to signal authentication failure - - user = null; - } - catch (Throwable e) - { - // Clear the user object to signal authentication failure - - user = null; - } - finally - { - try - { - if (tx != null) - { - tx.rollback(); - } - } - catch (Exception tex) - { - } - } - } - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("New Kerberos auth request from " + req.getRemoteHost() + " (" + - req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); - - // Send back a request for SPNEGO authentication - - resp.setHeader("WWW-Authenticate", "Negotiate"); - resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - - resp.flushBuffer(); - } - else - { - // Decode the received SPNEGO blob and validate - - final byte[] spnegoByts = Base64.decodeBase64( authHdr.substring(10).getBytes()); - - // Check the received SPNEGO token type - - int tokType = -1; - - try - { - tokType = SPNEGO.checkTokenType( spnegoByts, 0, spnegoByts.length); - } - catch ( IOException ex) - { - } - - // Check for a NegTokenInit blob - - if ( tokType == SPNEGO.NegTokenInit) - { - // Parse the SPNEGO security blob to get the Kerberos ticket - - NegTokenInit negToken = new NegTokenInit(); - - try - { - // Decode the security blob - - negToken.decode( spnegoByts, 0, spnegoByts.length); - - // Determine the authentication mechanism the client is using and logon - - String oidStr = null; - if ( negToken.numberOfOids() > 0) - oidStr = negToken.getOidAt( 0).toString(); - - if ( oidStr != null && (oidStr.equals( OID.ID_MSKERBEROS5) || oidStr.equals(OID.ID_KERBEROS5))) - { - // Kerberos logon - - if ( doKerberosLogon( negToken, req, resp, httpSess) != null) - { - // Allow the user to access the requested page - - chain.doFilter( req, resp); - } - else - { - // Send back a request for SPNEGO authentication - - resp.setHeader("WWW-Authenticate", "Negotiate"); - resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - - resp.flushBuffer(); - } - } - } - catch ( IOException ex) - { - // Log the error - - if ( logger.isDebugEnabled()) - logger.debug(ex); - } - } - else - { - // Unknown SPNEGO token type - - if ( logger.isDebugEnabled()) - logger.debug( "Unknown SPNEGO token type"); - - // Send back a request for SPNEGO authentication - - resp.setHeader("WWW-Authenticate", "Negotiate"); - resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - - resp.flushBuffer(); - } - } - } - - /** - * Delete the servlet filter - */ - public void destroy() + @Override + protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session) + throws IOException { + // Restart the login challenge process if validation fails + + restartLoginChallenge(res, session); } - /** - * JAAS callback handler - * - * @param callbacks Callback[] - * @exception IOException - * @exception UnsupportedCallbackException - */ - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException - { - // Process the callback list - - for (int i = 0; i < callbacks.length; i++) - { - // Request for user name - - if (callbacks[i] instanceof NameCallback) - { - NameCallback cb = (NameCallback) callbacks[i]; - cb.setName(m_accountName); - } - - // Request for password - else if (callbacks[i] instanceof PasswordCallback) - { - PasswordCallback cb = (PasswordCallback) callbacks[i]; - cb.setPassword(m_password.toCharArray()); - } - - // Request for realm - - else if (callbacks[i] instanceof RealmCallback) - { - RealmCallback cb = (RealmCallback) callbacks[i]; - cb.setText(m_krbRealm); - } - else - { - throw new UnsupportedCallbackException(callbacks[i]); - } - } - } - - /** - * Perform a Kerberos login and return an SPNEGO response - * - * @param negToken NegTokenInit - * @param req HttpServletRequest - * @param resp HttpServletResponse - * @param httpSess HttpSession - * @return NegTokenTarg - */ - private final NegTokenTarg doKerberosLogon( NegTokenInit negToken, HttpServletRequest req, HttpServletResponse resp, HttpSession httpSess) - { - // Authenticate the user - - KerberosDetails krbDetails = null; - NegTokenTarg negTokenTarg = null; - - UserTransaction tx = null; - - try - { - // Run the session setup as a privileged action - - SessionSetupPrivilegedAction sessSetupAction = new SessionSetupPrivilegedAction( m_accountName, negToken.getMechtoken()); - Object result = Subject.doAs( m_loginContext.getSubject(), sessSetupAction); - - if ( result != null) - { - // Access the Kerberos response - - krbDetails = (KerberosDetails) result; - - // Create the NegTokenTarg response blob - - negTokenTarg = new NegTokenTarg( SPNEGO.AcceptCompleted, OID.KERBEROS5, krbDetails.getResponseToken()); - - // Check if the user has been authenticated, if so then setup the user environment - - if ( negTokenTarg != null) - { - // Create a read transaction - - tx = m_transactionService.getUserTransaction(); - - NodeRef homeSpaceRef = null; - WebDAVUser user = null; - String userName = null; - - try - { - // Start the transaction - - tx.begin(); - - // Setup User object and Home space ID etc. - - NodeRef personNodeRef = m_personService.getPerson( krbDetails.getUserName()); - - // Use the system user to do the user name lookup - - m_authComponent.setSystemUserAsCurrentUser(); - - // User name should match the uid in the person entry found - - userName = (String) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - AuthenticationUtil.setCurrentUser( userName); - String currentTicket = m_authService.getCurrentTicket(); - user = new WebDAVUser(userName, currentTicket, personNodeRef); - - homeSpaceRef = (NodeRef) m_nodeService.getProperty( personNodeRef, ContentModel.PROP_HOMEFOLDER); - user.setHomeNode( homeSpaceRef); - - // Commit - - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Exception ex2) - { - logger.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 - - httpSess.setAttribute(AUTHENTICATION_USER, user); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("User " + userName + " logged on via Kerberos"); - - } - } - else - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug( "No SPNEGO response, Kerberos logon failed"); - } - } - catch (Exception ex) - { - // Log the error - - if ( logger.isDebugEnabled()) - logger.debug("Kerberos logon error", ex); - } - - // Return the response SPNEGO blob - - return negTokenTarg; - } - - /** - * Map the case insensitive logon name to the internal person object user name - * - * @param userName String - * @return String - */ - protected final String mapUserNameToPerson(String userName) - { - // Get the home folder for the user - - UserTransaction tx = m_transactionService.getUserTransaction(); - String personName = null; - - try - { - tx.begin(); - personName = m_personService.getUserIdentifier( userName); - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Throwable ex2) - { - logger.error("Failed to rollback transaction", ex2); - } - - // Re-throw the exception - - if (ex instanceof RuntimeException) - { - throw (RuntimeException) ex; - } - else - { - throw new RuntimeException("Error during execution of transaction.", ex); - } - } - - // Return the person name - - return personName; - } + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#getLogger() + */ + @Override + protected Log getLogger() { + return logger; + } } diff --git a/source/java/org/alfresco/repo/webdav/auth/NTLMAuthenticationFilter.java b/source/java/org/alfresco/repo/webdav/auth/NTLMAuthenticationFilter.java index 29cb4284cd..4cfd73751f 100644 --- a/source/java/org/alfresco/repo/webdav/auth/NTLMAuthenticationFilter.java +++ b/source/java/org/alfresco/repo/webdav/auth/NTLMAuthenticationFilter.java @@ -26,24 +26,14 @@ package org.alfresco.repo.webdav.auth; import java.io.IOException; -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; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import javax.transaction.UserTransaction; -import org.alfresco.jlan.server.auth.ntlm.NTLM; -import org.alfresco.jlan.server.auth.ntlm.NTLMMessage; -import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage; -import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage; -import org.alfresco.model.ContentModel; import org.alfresco.repo.SessionUser; -import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.service.cmr.repository.NodeRef; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -54,190 +44,35 @@ import org.apache.commons.logging.LogFactory; */ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter { - // 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"; - // Debug logging private static Log logger = LogFactory.getLog(NTLMAuthenticationFilter.class); - /** - * Run the filter + * Initialize the filter * - * @param sreq ServletRequest - * @param sresp ServletResponse - * @param chain FilterChain - * @exception IOException + * @param args FilterConfig * @exception ServletException */ - public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException, - ServletException + public void init(FilterConfig args) throws ServletException { - // Get the HTTP request/response/session - HttpServletRequest req = (HttpServletRequest) sreq; - HttpServletResponse resp = (HttpServletResponse) sresp; - HttpSession httpSess = req.getSession(true); + super.init(args); + + // Enable ticket based logons - // Check if there is an authorization header with an NTLM security blob - String authHdr = req.getHeader(AUTHORIZATION); - boolean reqAuth = (authHdr != null && authHdr.startsWith(AUTH_NTLM)); - - // Check if the user is already authenticated - WebDAVUser user = (WebDAVUser) httpSess.getAttribute(AUTHENTICATION_USER); - if (user != null && reqAuth == false) - { - try - { - if (logger.isDebugEnabled()) - logger.debug("User " + user.getUserName() + " validate ticket"); - - // Validate the user ticket - m_authService.validate( user.getTicket()); - reqAuth = false; - } - catch (AuthenticationException ex) - { - if (logger.isErrorEnabled()) - logger.error("Failed to validate user " + user.getUserName(), ex); - - reqAuth = true; - } - } - - // If the user has been validated and we do not require re-authentication then continue to - // the next filter - if (reqAuth == false && user != null) - { - if (logger.isDebugEnabled()) - logger.debug("Authentication not required, chaining ..."); - - // Chain to the next filter - chain.doFilter(sreq, sresp); - return; - } - - // Check the authorization header - if (authHdr == null) - { - // Check if the request includes an authentication ticket - String ticket = req.getParameter(ARG_TICKET); - if (ticket != null && ticket.length() != 0) - { - 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); - - if (user == null) - { - // Start a transaction - tx = m_transactionService.getUserTransaction(); - tx.begin(); - - // Need to create the User instance if not already available - String currentUsername = m_authService.getCurrentUserName(); - - NodeRef personRef = m_personService.getPerson(currentUsername); - user = new WebDAVUser(currentUsername, m_authService.getCurrentTicket(), personRef); - NodeRef homeRef = (NodeRef)m_nodeService.getProperty(personRef, ContentModel.PROP_HOMEFOLDER); - user.setHomeNode(homeRef); - - tx.commit(); - tx = null; - - // Store the User object in the Session - the authentication servlet will then proceed - req.getSession().setAttribute(AUTHENTICATION_USER, user); - } - - // Chain to the next filter - chain.doFilter(sreq, sresp); - return; - } - catch (AuthenticationException authErr) - { - if (logger.isDebugEnabled()) - logger.debug("Failed to authenticate user ticket: " + authErr.getMessage(), authErr); - } - catch (Throwable e) - { - if (logger.isDebugEnabled()) - logger.debug("Error during ticket validation and user creation: " + e.getMessage(), e); - } - finally - { - try - { - if (tx != null) - { - tx.rollback(); - } - } - catch (Exception tex) - { - } - } - } - - if (logger.isDebugEnabled()) - logger.debug("New NTLM auth request from " + req.getRemoteHost() + " (" + - req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); - - // Send back a request for NTLM authentication - restartLoginChallenge(resp, httpSess); - } - else - { - // Decode the received NTLM blob and validate - final byte[] ntlmByts = Base64.decodeBase64(authHdr.substring(5).getBytes()); - int ntlmTyp = NTLMMessage.isNTLMType(ntlmByts); - if (ntlmTyp == NTLM.Type1) - { - // Process the type 1 NTLM message - Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts); - processType1(type1Msg, req, resp, httpSess); - } - else if (ntlmTyp == NTLM.Type3) - { - // Process the type 3 NTLM message - Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts); - processType3(type3Msg, req, resp, httpSess, chain); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("NTLM blob not handled, restarting login challenge."); - - restartLoginChallenge(resp, httpSess); - } - } - } - - /* (non-Javadoc) - * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getSessionUser(javax.servlet.http.HttpSession) - */ - @Override - protected SessionUser getSessionUser(HttpSession session) - { - return (SessionUser)session.getAttribute(AUTHENTICATION_USER); + setTicketLogons( true); } - /* (non-Javadoc) - * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpSession) - */ - @Override - protected void onValidate(HttpServletRequest req, HttpSession session) - { - // nothing to do for webdav filter - } - + /* (non-Javadoc) + * @see org.alfresco.repo.webdav.auth.BaseSSOAuthenticationFilter#createUserObject(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + @Override + protected SessionUser createUserObject(String userName, String ticket, NodeRef personNode, String homeSpace) { + + // Create a WebDAV user object + + return new WebDAVUser( userName, ticket, personNode); + } + /* (non-Javadoc) * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession) */ @@ -245,88 +80,11 @@ public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session) throws IOException { - // restart the login challenge process if validation fails + // Restart the login challenge process if validation fails + restartLoginChallenge(res, session); } - /* (non-Javadoc) - * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#createUserEnvironment(javax.servlet.http.HttpSession, java.lang.String) - */ - @Override - protected SessionUser createUserEnvironment(HttpSession session, String userName) - throws IOException, ServletException - { - SessionUser user = null; - - UserTransaction tx = m_transactionService.getUserTransaction(); - - try - { - tx.begin(); - - // Setup User object and Home space ID etc. - NodeRef personNodeRef = m_personService.getPerson(userName); - - // Use the system user context to do the user lookup - m_authComponent.setCurrentUser(m_authComponent.getSystemUserName()); - - // User name should match the uid in the person entry found - m_authComponent.setSystemUserAsCurrentUser(); - userName = (String) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - - m_authComponent.setCurrentUser(userName); - String currentTicket = m_authService.getCurrentTicket(); - user = new WebDAVUser(userName, currentTicket, personNodeRef); - - NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); - ((WebDAVUser)user).setHomeNode(homeSpaceRef); - - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Exception ex2) - { - logger.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 on the session - session.setAttribute(AUTHENTICATION_USER, user); - - return user; - } - - /* (non-Javadoc) - * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onLoginComplete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) - */ - @Override - protected boolean onLoginComplete(HttpServletRequest req, HttpServletResponse res) throws IOException - { - // no futher processing to do, allow to complete - return true; - } - /* (non-Javadoc) * @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getLogger() */