Merged V3.0 to HEAD

11355: NTLMLogoDetails and Type2NTLMMessage are now Serializable, as they may be stored in an HTTP session. JLAN-43.
   11386: Refactor of repository tier NTLM authentication filters.
   11387: Integration of NTLMv2 message support for NTLM authentication filters
   11400: Fixed NTLMv2 detection for Firefox, it does not send the 128Bit flag.
   11401: Second round of NTLM authentication filter refactoring for repo tier.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12425 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2008-12-17 05:47:38 +00:00
parent 16c2e77852
commit 9bce6404be
6 changed files with 1208 additions and 748 deletions

View File

@@ -0,0 +1,50 @@
/*
* 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;
import java.io.Serializable;
/**
* Contract implemented by any object that represents an Alfresco "user" that
* can be persisted in an HTTP Session.
*
* @author Kevin Roast
*/
public interface SessionUser extends Serializable
{
/**
* Return the user name
*
* @return user name
*/
String getUserName();
/**
* Return the ticket
*
* @return ticket
*/
String getTicket();
}

View File

@@ -71,7 +71,7 @@ public class LoginTicket extends DeclarativeWebScript
}
// construct model for ticket
Map<String, Object> model = new HashMap<String, Object>(7, 1.0f);
Map<String, Object> model = new HashMap<String, Object>(1, 1.0f);
model.put("ticket", ticket);
try
@@ -86,7 +86,7 @@ public class LoginTicket extends DeclarativeWebScript
status.setMessage("Ticket not found");
}
}
catch(AuthenticationException e)
catch (AuthenticationException e)
{
status.setRedirect(true);
status.setCode(HttpServletResponse.SC_NOT_FOUND);

View File

@@ -81,7 +81,7 @@ public class LoginTicketDelete extends DeclarativeWebScript
}
// construct model for ticket
Map<String, Object> model = new HashMap<String, Object>(7, 1.0f);
Map<String, Object> model = new HashMap<String, Object>(1, 1.0f);
model.put("ticket", ticket);
try

View File

@@ -0,0 +1,997 @@
/*
* 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 java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.transaction.Status;
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.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
{
// NTLM authentication session object names
public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess";
public static final String NTLM_AUTH_DETAILS = "_alfNTLMDetails";
protected static final String WWW_AUTHENTICATE = "WWW-Authenticate";
protected static final String AUTHORIZATION = "Authorization";
protected static final String AUTH_NTLM = "NTLM";
// NTLM flags mask for use with an authentication component that supports MD4 hashed password
private static final int NTLM_FLAGS_MD4 = NTLM.Flag56Bit +
NTLM.Flag128Bit +
NTLM.FlagLanManKey +
NTLM.FlagNegotiateNTLM +
NTLM.FlagNTLM2Key +
NTLM.FlagNegotiateUnicode;
// NTLM flags mask for use with an authentication component that uses passthru auth
private static final int NTLM_FLAGS_PASSTHRU = NTLM.Flag56Bit +
NTLM.FlagLanManKey +
NTLM.FlagNegotiateNTLM +
NTLM.FlagNegotiateOEM +
NTLM.FlagNegotiateUnicode;
// 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();
// Random number generator used to generate challenge keys
private Random m_random = new Random(System.currentTimeMillis());
// 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;
// Allow guest access, map unknown users to the guest account
private boolean m_mapUnknownUserToGuest = false;
/**
* 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);
// 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);
}
}
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)
{
m_allowGuest = Boolean.parseBoolean(guestAccess);
if (getLogger().isDebugEnabled() && m_allowGuest)
getLogger().debug("NTLM filter guest access allowed");
}
// Check if unknown users should be mapped to guest access
String mapUnknownToGuest = args.getInitParameter("MapUnknownUserToGuest");
if (mapUnknownToGuest != null)
{
m_mapUnknownUserToGuest = Boolean.parseBoolean(mapUnknownToGuest);
if (getLogger().isDebugEnabled() && m_mapUnknownUserToGuest)
getLogger().debug("NTLM filter map unknown users to guest");
}
// Set the NTLM flags depending on the authentication component supporting MD4 passwords,
// or is using passthru auth
if (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER)
{
// Allow the client to use an NTLMv2 logon
m_ntlmFlags = NTLM_FLAGS_MD4;
}
else
{
// Only allows NTLMv1 type logons as passthru authentication is being used
m_ntlmFlags = NTLM_FLAGS_PASSTHRU;
}
}
/**
* Delete the servlet filter
*/
public void destroy()
{
}
/**
* Process a type 1 NTLM message
*
* @param type1Msg Type1NTLMMessage
* @param req HttpServletRequest
* @param res HttpServletResponse
* @param session HttpSession
* @exception IOException
*/
protected void processType1(Type1NTLMMessage type1Msg, HttpServletRequest req,
HttpServletResponse res, HttpSession session) throws IOException
{
Log logger = getLogger();
if (logger.isDebugEnabled())
logger.debug("Received type1 " + type1Msg);
// Get the existing NTLM details
NTLMLogonDetails ntlmDetails = null;
if (session != null)
{
ntlmDetails = (NTLMLogonDetails)session.getAttribute(NTLM_AUTH_DETAILS);
}
// Check if cached logon details are available
if (ntlmDetails != null && ntlmDetails.hasType2Message() &&
ntlmDetails.hasNTLMHashedPassword() && ntlmDetails.hasAuthenticationToken())
{
// Get the authentication server type2 response
Type2NTLMMessage cachedType2 = ntlmDetails.getType2Message();
byte[] type2Bytes = cachedType2.getBytes();
String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes));
if (logger.isDebugEnabled())
logger.debug("Sending cached NTLM type2 to client - " + cachedType2);
// Send back a request for NTLM authentication
res.setHeader(WWW_AUTHENTICATE, ntlmBlob);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.flushBuffer();
}
else
{
// Clear any cached logon details
session.removeAttribute(NTLM_AUTH_DETAILS);
// Set the 8 byte challenge for the new logon request
byte[] challenge = null;
NTLMPassthruToken authToken = null;
if (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER)
{
// Generate a random 8 byte challenge
challenge = new byte[8];
DataPacker.putIntelLong(m_random.nextLong(), challenge, 0);
}
else
{
// Get the client domain
String domain = type1Msg.getDomain();
if (domain == null || domain.length() == 0)
{
domain = mapClientAddressToDomain(req.getRemoteAddr());
}
if (logger.isDebugEnabled())
logger.debug("Client domain " + domain);
// Create an authentication token for the new logon
authToken = new NTLMPassthruToken(domain);
// Run the first stage of the passthru authentication to get the challenge
m_authComponent.authenticate(authToken);
// Get the challenge from the token
if (authToken.getChallenge() != null)
{
challenge = authToken.getChallenge().getBytes();
}
}
// Get the flags from the client request and mask out unsupported features
int ntlmFlags = type1Msg.getFlags() & m_ntlmFlags;
// Build a type2 message to send back to the client, containing the challenge
List<TargetInfo> tList = new ArrayList<TargetInfo>();
tList.add(new TargetInfo(NTLM.TargetServer, m_srvName));
Type2NTLMMessage type2Msg = new Type2NTLMMessage();
type2Msg.buildType2(ntlmFlags, m_srvName, challenge, null, tList);
// Store the NTLM logon details, cache the type2 message, and token if using passthru
ntlmDetails = new NTLMLogonDetails();
ntlmDetails.setType2Message(type2Msg);
ntlmDetails.setAuthenticationToken(authToken);
session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails);
if (logger.isDebugEnabled())
logger.debug("Sending NTLM type2 to client - " + type2Msg);
// Send back a request for NTLM authentication
byte[] type2Bytes = type2Msg.getBytes();
String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes));
res.setHeader(WWW_AUTHENTICATE, ntlmBlob);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
res.flushBuffer();
}
}
/**
* Process a type 3 NTLM message
*
* @param type3Msg Type3NTLMMessage
* @param req HttpServletRequest
* @param res HttpServletResponse
* @param session HttpSession
* @param chain FilterChain
*
* @exception IOException
* @exception ServletException
*/
protected void processType3(Type3NTLMMessage type3Msg, HttpServletRequest req, HttpServletResponse res,
HttpSession session, FilterChain chain) throws IOException, ServletException
{
Log logger = getLogger();
if (logger.isDebugEnabled())
logger.debug("Received type3 " + type3Msg);
// Get the existing NTLM details
NTLMLogonDetails ntlmDetails = null;
SessionUser user = null;
if (session != null)
{
ntlmDetails = (NTLMLogonDetails)session.getAttribute(NTLM_AUTH_DETAILS);
user = getSessionUser(session);
}
// Get the NTLM logon details
String userName = type3Msg.getUserName();
String workstation = type3Msg.getWorkstation();
String domain = type3Msg.getDomain();
boolean authenticated = false;
// Check if we are using cached details for the authentication
if (user != null && ntlmDetails != null && ntlmDetails.hasNTLMHashedPassword())
{
// Check if the received NTLM hashed password matches the cached password
byte[] ntlmPwd = type3Msg.getNTLMHash();
byte[] cachedPwd = ntlmDetails.getNTLMHashedPassword();
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;
}
}
}
if (logger.isDebugEnabled())
logger.debug("Using cached NTLM hash, authenticated = " + authenticated);
try
{
if (logger.isDebugEnabled())
logger.debug("User " + user.getUserName() + " validate ticket");
// Validate the user ticket
m_authService.validate(user.getTicket());
onValidate(req, session);
}
catch (AuthenticationException ex)
{
if (logger.isErrorEnabled())
logger.error("Failed to validate user " + user.getUserName(), ex);
onValidateFailed(req, res, session);
return;
}
// Allow the user to access the requested page
chain.doFilter(req, res);
return;
}
else
{
// Check if we are using local MD4 password hashes or passthru authentication
if (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER)
{
// Check if guest logons are allowed and this is a guest logon
if (m_allowGuest && userName.equalsIgnoreCase(m_authComponent.getGuestUserName()))
{
// Indicate that the user has been authenticated
authenticated = true;
if (logger.isDebugEnabled())
logger.debug("Guest logon");
}
else
{
// Get the stored MD4 hashed password for the user, or null if the user does not exist
String md4hash = getMD4Hash(userName);
if (md4hash != null)
{
authenticated = validateLocalHashedPassword(type3Msg, ntlmDetails, authenticated, md4hash);
}
else
{
// Check if unknown users should be logged on as guest
if (m_mapUnknownUserToGuest)
{
// Reset the user name to be the guest user
userName = m_authComponent.getGuestUserName();
authenticated = true;
if (logger.isDebugEnabled())
logger.debug("User " + userName + " logged on as guest, no Alfresco account");
}
else
{
if (logger.isDebugEnabled())
logger.debug("User " + userName + " does not have Alfresco account");
// Bypass NTLM authentication and display the logon screen,
// as user account does not exist in Alfresco
authenticated = false;
}
}
}
}
else
{
// Determine if the client sent us NTLMv1 or NTLMv2
if (type3Msg.hasFlag(NTLM.Flag128Bit) && type3Msg.hasFlag(NTLM.FlagNTLM2Key) ||
(type3Msg.getNTLMHash() != null && type3Msg.getNTLMHash().length > 24))
{
// Cannot accept NTLMv2 if we are using passthru auth
if (logger.isErrorEnabled())
logger.error("Client " + workstation + " using NTLMv2 logon, not valid with passthru authentication");
}
else
{
// Passthru mode, send the hashed password details to the passthru authentication server
NTLMPassthruToken authToken = (NTLMPassthruToken) ntlmDetails.getAuthenticationToken();
authToken.setUserAndPassword( type3Msg.getUserName(), type3Msg.getNTLMHash(), PasswordEncryptor.NTLM1);
try
{
// Run the second stage of the passthru authentication
m_authComponent.authenticate(authToken);
authenticated = true;
// Check if the user has been logged on as guest
if (authToken.isGuestLogon())
{
userName = m_authComponent.getGuestUserName();
}
// Set the authentication context
m_authComponent.setCurrentUser(userName);
}
catch (BadCredentialsException ex)
{
if (logger.isDebugEnabled())
logger.debug("Authentication failed, " + ex.getMessage());
}
catch (AuthenticationException ex)
{
if (logger.isDebugEnabled())
logger.debug("Authentication failed, " + ex.getMessage());
}
finally
{
// Clear the authentication token from the NTLM details
ntlmDetails.setAuthenticationToken(null);
}
}
}
// Check if the user has been authenticated, if so then setup the user environment
if (authenticated == true)
{
if (user == null)
{
user = createUserEnvironment(session, userName);
}
else
{
// user already exists - revalidate ticket to authenticate the current user thread
try
{
m_authService.validate(user.getTicket());
}
catch (AuthenticationException ex)
{
if (logger.isErrorEnabled())
logger.error("Failed to validate user " + user.getUserName(), ex);
onValidateFailed(req, res, session);
return;
}
}
onValidate(req, session);
// Update the NTLM logon details in the session
if (ntlmDetails == null)
{
// No cached NTLM details
ntlmDetails = new NTLMLogonDetails( userName, workstation, domain, false, m_srvName);
ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash());
session.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails);
if (logger.isDebugEnabled())
logger.debug("No cached NTLM details, created");
}
else
{
// Update the cached NTLM details
ntlmDetails.setDetails(userName, workstation, domain, false, m_srvName);
ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash());
if (logger.isDebugEnabled())
logger.debug("Updated cached NTLM details");
}
if (logger.isDebugEnabled())
logger.debug("User logged on via NTLM, " + ntlmDetails);
if (onLoginComplete(req, res))
{
// Allow the user to access the requested page
chain.doFilter(req, res);
}
}
else
{
restartLoginChallenge(res, session);
}
}
}
/**
* 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
*
* @param type3Msg
* @param ntlmDetails
* @param authenticated
* @param md4hash
*
* @return true if password hash is valid, false otherwise
*/
protected boolean validateLocalHashedPassword(Type3NTLMMessage type3Msg, NTLMLogonDetails ntlmDetails, boolean authenticated, String md4hash)
{
// Determine if the client sent us NTLMv1 or NTLMv2
if (type3Msg.hasFlag(NTLM.FlagNTLM2Key))
{
// Determine if the client sent us an NTLMv2 blob or an NTLMv2 session key
if (type3Msg.getNTLMHashLength() > 24)
{
// Looks like an NTLMv2 blob
authenticated = checkNTLMv2(md4hash, ntlmDetails.getChallengeKey(), type3Msg);
if (getLogger().isDebugEnabled())
getLogger().debug((authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv2");
}
else
{
// Looks like an NTLMv2 session key
authenticated = checkNTLMv2SessionKey(md4hash, ntlmDetails.getChallengeKey(), type3Msg);
if (getLogger().isDebugEnabled())
getLogger().debug((authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv2SessKey");
}
}
else
{
// Looks like an NTLMv1 blob
authenticated = checkNTLMv1(md4hash, ntlmDetails.getChallengeKey(), type3Msg);
if (getLogger().isDebugEnabled())
getLogger().debug((authenticated ? "Logged on" : "Logon failed") + " using NTLMSSP/NTLMv1");
}
return authenticated;
}
/**
* Perform an NTLMv1 hashed password check
*
* @param String md4hash
* @param byte[] challenge
* @param Type3NTLMMessage type3Msg
* @return boolean
*/
protected final boolean checkNTLMv1(String md4hash, byte[] challenge, Type3NTLMMessage type3Msg)
{
// Generate the local encrypted password using the challenge that was sent to the client
byte[] p21 = new byte[21];
byte[] md4byts = m_md4Encoder.decodeHash(md4hash);
System.arraycopy(md4byts, 0, p21, 0, 16);
// Generate the local hash of the password using the same challenge
byte[] localHash = null;
try
{
localHash = m_encryptor.doNTLM1Encryption(p21, challenge);
}
catch (NoSuchAlgorithmException ex)
{
}
// Validate the password
byte[] clientHash = type3Msg.getNTLMHash();
if (clientHash != null && localHash != null && clientHash.length == localHash.length)
{
int i = 0;
while (i < clientHash.length && clientHash[i] == localHash[i])
{
i++;
}
if (i == clientHash.length)
{
// Hashed passwords match
return true;
}
}
// Hashed passwords do not match
return false;
}
/**
* Perform an NTLMv2 check
*
* @param String md4hash
* @param byte[] challenge
* @param Type3NTLMMessage type3Msg
* @return boolean
*/
protected final boolean checkNTLMv2(String md4hash, byte[] challenge, Type3NTLMMessage type3Msg)
{
try
{
// Generate the v2 hash using the challenge that was sent to the client
byte[] v2hash = m_encryptor.doNTLM2Encryption(m_md4Encoder.decodeHash(md4hash), type3Msg.getUserName(), type3Msg.getDomain());
// Get the NTLMv2 blob sent by the client and the challenge that was sent by the server
NTLMv2Blob v2blob = new NTLMv2Blob(type3Msg.getNTLMHash());
// Calculate the HMAC of the received blob and compare
byte[] srvHmac = v2blob.calculateHMAC(challenge, v2hash);
byte[] clientHmac = v2blob.getHMAC();
if (clientHmac != null && srvHmac != null && clientHmac.length == srvHmac.length)
{
int i = 0;
while (i < clientHmac.length && clientHmac[i] == srvHmac[i])
{
i++;
}
if (i == clientHmac.length)
{
// HMAC matches the client, user authenticated
return true;
}
}
}
catch (Exception ex)
{
if (getLogger().isDebugEnabled())
getLogger().debug(ex);
}
// NTLMv2 check failed
return false;
}
/**
* Perform an NTLMv2 session key check
*
* @param String md4hash
* @param byte[] challenge
* @param Type3NTLMMessage type3Msg
* @return boolean
*/
protected final boolean checkNTLMv2SessionKey(String md4hash, byte[] challenge, Type3NTLMMessage type3Msg)
{
// Create the value to be encrypted by appending the server challenge and client challenge
// and applying an MD5 digest
byte[] nonce = new byte[16];
System.arraycopy(challenge, 0, nonce, 0, 8);
System.arraycopy(type3Msg.getLMHash(), 0, nonce, 8, 8);
MessageDigest md5 = null;
byte[] v2challenge = new byte[8];
try
{
// Create the MD5 digest
md5 = MessageDigest.getInstance("MD5");
// Apply the MD5 digest to the nonce
md5.update(nonce);
byte[] md5nonce = md5.digest();
// We only want the first 8 bytes
System.arraycopy(md5nonce, 0, v2challenge, 0, 8);
}
catch (NoSuchAlgorithmException ex)
{
// Log the error
getLogger().error(ex);
}
// Generate the local encrypted password using the MD5 generated challenge
byte[] p21 = new byte[21];
byte[] md4byts = m_md4Encoder.decodeHash(md4hash);
System.arraycopy(md4byts, 0, p21, 0, 16);
// Generate the local hash of the password
byte[] localHash = null;
try
{
localHash = m_encryptor.doNTLM1Encryption(p21, v2challenge);
}
catch (NoSuchAlgorithmException ex)
{
// Log the error
getLogger().error(ex);
}
// Validate the password
byte[] clientHash = type3Msg.getNTLMHash();
if (clientHash != null && localHash != null && clientHash.length == localHash.length)
{
int i = 0;
while (i < clientHash.length && clientHash[i] == localHash[i])
{
i++;
}
if (i == clientHash.length)
{
// Hashed password check successful
return true;
}
}
// Password check failed
return false;
}
/**
* Get the stored MD4 hashed password for the user, or null if the user does not exist
*
* @param userName
* @param md4hash
*
* @return MD4 hash or null
*/
protected String getMD4Hash(String userName)
{
String md4hash = null;
// Wrap the auth component calls in a transaction
UserTransaction tx = m_transactionService.getUserTransaction();
try
{
tx.begin();
// Get the stored MD4 hashed password for the user, or null if the user does not exist
md4hash = m_authComponent.getMD4HashedPassword(userName);
}
catch (Throwable ex)
{
if (getLogger().isDebugEnabled())
getLogger().debug(ex);
}
finally
{
// Rollback/commit the transaction if still valid
if (tx != null)
{
try
{
// Commit or rollback the transaction
if (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK ||
tx.getStatus() == Status.STATUS_ROLLEDBACK ||
tx.getStatus() == Status.STATUS_ROLLING_BACK)
{
// Transaction is marked for rollback
tx.rollback();
}
else
{
// Commit the transaction
tx.commit();
}
}
catch (Throwable ex)
{
if (getLogger().isDebugEnabled())
getLogger().debug(ex);
}
}
}
return md4hash;
}
/**
* @param resp
* @param httpSess
* @throws IOException
*/
protected void restartLoginChallenge(HttpServletResponse res, HttpSession session) throws IOException
{
// Remove any existing session and NTLM details from the session
session.removeAttribute(NTLM_AUTH_SESSION);
session.removeAttribute(NTLM_AUTH_DETAILS);
// Force the logon to start again
res.setHeader(WWW_AUTHENTICATE, AUTH_NTLM);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
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;
}
// 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;
}
protected abstract Log getLogger();
}

View File

@@ -25,205 +25,44 @@
package org.alfresco.repo.webdav.auth;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
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;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
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.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.util.DataPacker;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.SessionUser;
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.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.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.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* NTLM Authentication Filter Class
* WebDav NTLM Authentication Filter Class
*
* @author GKSpencer
*/
public class NTLMAuthenticationFilter implements Filter
public class NTLMAuthenticationFilter extends BaseNTLMAuthenticationFilter
{
// NTLM authentication session object names
public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess";
public static final String NTLM_AUTH_DETAILS = "_alfNTLMDetails";
// 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";
// NTLM flags mask, used to mask out features that are not supported
private static final int NTLM_FLAGS = NTLM.Flag56Bit + NTLM.FlagLanManKey + NTLM.FlagNegotiateNTLM +
NTLM.FlagNegotiateOEM + NTLM.FlagNegotiateUnicode;
// Debug logging
private static Log logger = LogFactory.getLog(NTLMAuthenticationFilter.class);
// Servlet context, required to get authentication service
private ServletContext m_context;
// File server configuration
private ServerConfigurationBean m_srvConfig;
// Various services required by NTLM authenticator
private AuthenticationService m_authService;
private AuthenticationComponent m_authComponent;
private PersonService m_personService;
private NodeService m_nodeService;
private TransactionService m_transactionService;
// Password encryptor
private PasswordEncryptor m_encryptor = new PasswordEncryptor();
// Allow guest access
private boolean m_allowGuest;
// Random number generator used to generate challenge keys
private Random m_random = new Random(System.currentTimeMillis());
// MD4 hash decoder
private MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl();
// Local server name, from either the file servers config or DNS host name
private String m_srvName;
/**
* 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 = (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.getLocalServerName( true);
}
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("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)
{
m_allowGuest = Boolean.parseBoolean(guestAccess);
// Debug
if ( logger.isDebugEnabled() && m_allowGuest)
logger.debug("NTLM filter guest access allowed");
}
}
/**
* Run the filter
@@ -238,41 +77,30 @@ public class NTLMAuthenticationFilter implements Filter
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 NTLM security blob
String authHdr = req.getHeader("Authorization");
boolean reqAuth = false;
if ( authHdr != null && authHdr.startsWith("NTLM"))
reqAuth = true;
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)
if (user != null && reqAuth == false)
{
try
{
// Debug
if ( logger.isDebugEnabled())
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())
if (logger.isErrorEnabled())
logger.error("Failed to validate user " + user.getUserName(), ex);
reqAuth = true;
@@ -281,33 +109,24 @@ public class NTLMAuthenticationFilter implements Filter
// 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 (reqAuth == false && user != null)
{
// Debug
if ( logger.isDebugEnabled())
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 (authHdr == null)
{
// Debug
if ( logger.isDebugEnabled())
// 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);
@@ -315,53 +134,42 @@ public class NTLMAuthenticationFilter implements Filter
try
{
// Validate the ticket
m_authService.validate(ticket);
// Need to create the User instance if not already available
String currentUsername = m_authService.getCurrentUserName();
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);
// Check that the home space node exists - else Login cannot proceed
if (m_nodeService.exists(homeRef) == false)
{
throw new InvalidNodeRefException(homeRef);
}
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);
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;
if (logger.isDebugEnabled())
logger.debug("Failed to authenticate user ticket: " + authErr.getMessage(), authErr);
}
catch (Throwable e)
{
// Clear the user object to signal authentication failure
user = null;
if (logger.isDebugEnabled())
logger.debug("Error during ticket validation and user creation: " + e.getMessage(), e);
}
finally
{
@@ -378,428 +186,80 @@ public class NTLMAuthenticationFilter implements Filter
}
}
// Debug
if ( logger.isDebugEnabled())
if (logger.isDebugEnabled())
logger.debug("New NTLM auth request from " + req.getRemoteHost() + " (" +
req.getRemoteAddr() + ":" + req.getRemotePort() + ")");
// Send back a request for NTLM authentication
resp.setHeader("WWW-Authenticate", "NTLM");
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.flushBuffer();
return;
restartLoginChallenge(resp, httpSess);
}
else
{
// Decode the received NTLM blob and validate
byte[] ntlmByts = Base64.decodeBase64( authHdr.substring(5).getBytes());
final byte[] ntlmByts = Base64.decodeBase64(authHdr.substring(5).getBytes());
int ntlmTyp = NTLMMessage.isNTLMType(ntlmByts);
if ( ntlmTyp == NTLM.Type1)
if (ntlmTyp == NTLM.Type1)
{
// Process the type 1 NTLM message
Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts);
processType1(type1Msg, req, resp, httpSess);
return;
}
else if ( ntlmTyp == NTLM.Type3)
else if (ntlmTyp == NTLM.Type3)
{
// Process the type 3 NTLM message
Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts);
processType3(type3Msg, req, resp, httpSess, chain);
return;
}
else
{
if (logger.isDebugEnabled())
logger.debug("NTLM blob not handled, restarting login challenge.");
restartLoginChallenge(resp, httpSess);
}
}
}
// Remove any existing session and NTLM details from the session
httpSess.removeAttribute(NTLM_AUTH_SESSION);
httpSess.removeAttribute(NTLM_AUTH_DETAILS);
// Force the logon to start again
resp.setHeader("WWW-Authenticate", "NTLM");
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.flushBuffer();
}
/**
* Delete the servlet filter
/* (non-Javadoc)
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getSessionUser(javax.servlet.http.HttpSession)
*/
public void destroy()
@Override
protected SessionUser getSessionUser(HttpSession session)
{
return (SessionUser)session.getAttribute(AUTHENTICATION_USER);
}
/**
* Process a type 1 NTLM message
*
* @param type1Msg Type1NTLMMessage
* @param req HttpServletRequest
* @param resp HttpServletResponse
* @param httpSess HttpSession
* @exception IOException
/* (non-Javadoc)
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpSession)
*/
private void processType1(Type1NTLMMessage type1Msg, HttpServletRequest req, HttpServletResponse resp,
HttpSession httpSess) throws IOException
@Override
protected void onValidate(HttpServletRequest req, HttpSession session)
{
// Debug
if ( logger.isDebugEnabled())
logger.debug("Received type1 " + type1Msg);
// Get the existing NTLM details
NTLMLogonDetails ntlmDetails = null;
if ( httpSess != null)
{
ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS);
// nothing to do for webdav filter
}
// Check if cached logon details are available
if ( ntlmDetails != null && ntlmDetails.hasType2Message() && ntlmDetails.hasNTLMHashedPassword() &&
ntlmDetails.hasAuthenticationToken())
{
// Get the authentication server type2 response
Type2NTLMMessage cachedType2 = ntlmDetails.getType2Message();
byte[] type2Bytes = cachedType2.getBytes();
String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes));
// Debug
if ( logger.isDebugEnabled())
logger.debug("Sending cached NTLM type2 to client - " + cachedType2);
// Send back a request for NTLM authentication
resp.setHeader("WWW-Authenticate", ntlmBlob);
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.flushBuffer();
return;
}
else
{
// Clear any cached logon details
httpSess.removeAttribute(NTLM_AUTH_DETAILS);
// Set the 8 byte challenge for the new logon request
byte[] challenge = null;
NTLMPassthruToken authToken = null;
if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER)
{
// Generate a random 8 byte challenge
challenge = new byte[8];
DataPacker.putIntelLong(m_random.nextLong(), challenge, 0);
}
else
{
// Create an authentication token for the new logon
authToken = new NTLMPassthruToken();
// Run the first stage of the passthru authentication to get the challenge
m_authComponent.authenticate( authToken);
// Get the challenge from the token
if ( authToken.getChallenge() != null)
challenge = authToken.getChallenge().getBytes();
}
// Get the flags from the client request and mask out unsupported features
int ntlmFlags = type1Msg.getFlags() & NTLM_FLAGS;
// Build a type2 message to send back to the client, containing the challenge
List<TargetInfo> tList = new ArrayList<TargetInfo>();
tList.add(new TargetInfo(NTLM.TargetServer, m_srvName));
Type2NTLMMessage type2Msg = new Type2NTLMMessage();
type2Msg.buildType2(ntlmFlags, m_srvName, challenge, null, tList);
// Store the NTLM logon details, cache the type2 message, and token if using passthru
ntlmDetails = new NTLMLogonDetails();
ntlmDetails.setType2Message( type2Msg);
ntlmDetails.setAuthenticationToken(authToken);
httpSess.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails);
// Debug
if ( logger.isDebugEnabled())
logger.debug("Sending NTLM type2 to client - " + type2Msg);
// Send back a request for NTLM authentication
byte[] type2Bytes = type2Msg.getBytes();
String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes));
resp.setHeader("WWW-Authenticate", ntlmBlob);
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.flushBuffer();
return;
}
}
/**
* Process a type 3 NTLM message
*
* @param type3Msg Type3NTLMMessage
* @param req HttpServletRequest
* @param resp HttpServletResponse
* @param httpSess HttpSession
* @param chain FilterChain
* @exception IOException
* @exception ServletException
/* (non-Javadoc)
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#onValidateFailed(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.http.HttpSession)
*/
private void processType3(Type3NTLMMessage type3Msg, HttpServletRequest req, HttpServletResponse resp,
HttpSession httpSess, FilterChain chain) throws IOException, ServletException
@Override
protected void onValidateFailed(HttpServletRequest req, HttpServletResponse res, HttpSession session)
throws IOException
{
// Debug
// restart the login challenge process if validation fails
restartLoginChallenge(res, session);
}
if ( logger.isDebugEnabled())
logger.debug("Received type3 " + type3Msg);
// Get the existing NTLM details
NTLMLogonDetails ntlmDetails = null;
WebDAVUser user = null;
if ( httpSess != null)
/* (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
{
ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS);
user = (WebDAVUser) httpSess.getAttribute(AUTHENTICATION_USER);
}
Log logger = getLogger();
// Get the NTLM logon details
SessionUser user = null;
String userName = type3Msg.getUserName();
String workstation = type3Msg.getWorkstation();
String domain = type3Msg.getDomain();
boolean authenticated = false;
// Check if we are using cached details for the authentication
if ( user != null && ntlmDetails != null && ntlmDetails.hasNTLMHashedPassword())
{
// Check if the received NTLM hashed password matches the cached password
byte[] ntlmPwd = type3Msg.getNTLMHash();
byte[] cachedPwd = ntlmDetails.getNTLMHashedPassword();
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;
}
}
}
// Debug
if ( logger.isDebugEnabled())
logger.debug("Using cached NTLM hash, authenticated = " + authenticated);
try
{
// Debug
if ( logger.isDebugEnabled())
logger.debug("User " + user.getUserName() + " validate ticket");
// Validate the user ticket
m_authService.validate( user.getTicket());
}
catch (AuthenticationException ex)
{
if ( logger.isErrorEnabled())
logger.error("Failed to validate user " + user.getUserName(), ex);
// Restart the authentication
httpSess.removeAttribute(NTLM_AUTH_SESSION);
httpSess.removeAttribute(NTLM_AUTH_DETAILS);
// Force the logon to start again
resp.setHeader("WWW-Authenticate", "NTLM");
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.flushBuffer();
return;
}
// Allow the user to access the requested page
chain.doFilter( req, resp);
return;
}
else
{
// Cehck if we are using local MD4 password hashes or passthru authentication
if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER)
{
// Wrap the auth componenet calls in a transaction
UserTransaction tx = m_transactionService.getUserTransaction();
String md4hash = null;
try
{
tx.begin();
// Get the stored MD4 hashed password for the user, or null if the user does not exist
md4hash = m_authComponent.getMD4HashedPassword(userName);
}
catch ( Throwable ex) {
if ( logger.isDebugEnabled())
logger.debug(ex);
}
finally {
// Rollback/commit the transaction if still valid
if ( tx != null) {
try
{
// Commit or rollback the transaction
if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK ||
tx.getStatus() == Status.STATUS_ROLLEDBACK ||
tx.getStatus() == Status.STATUS_ROLLING_BACK)
{
// Transaction is marked for rollback
tx.rollback();
}
else
{
// Commit the transaction
tx.commit();
}
}
catch (Throwable ex) {
if ( logger.isDebugEnabled())
logger.debug(ex);
}
}
}
// Check if we got a valid MD4 hashed password
if ( md4hash != null)
{
// Generate the local encrypted password using the challenge that was sent to the client
byte[] p21 = new byte[21];
byte[] md4byts = m_md4Encoder.decodeHash(md4hash);
System.arraycopy(md4byts, 0, p21, 0, 16);
// Generate the local hash of the password using the same challenge
byte[] localHash = null;
try
{
localHash = m_encryptor.doNTLM1Encryption(p21, ntlmDetails.getChallengeKey());
}
catch (NoSuchAlgorithmException ex)
{
}
// Validate the password
byte[] clientHash = type3Msg.getNTLMHash();
if ( clientHash != null && localHash != null && clientHash.length == localHash.length)
{
int i = 0;
while ( i < clientHash.length && clientHash[i] == localHash[i])
i++;
if ( i == clientHash.length)
authenticated = true;
}
}
else
{
// Debug
if ( logger.isDebugEnabled())
logger.debug("User " + userName + " does not have Alfresco account");
// Bypass NTLM authentication and display the logon screen, user account does not
// exist in Alfresco
authenticated = false;
}
}
else
{
// Passthru mode, send the hashed password details to the passthru authentication server
NTLMPassthruToken authToken = (NTLMPassthruToken) ntlmDetails.getAuthenticationToken();
authToken.setUserAndPassword( type3Msg.getUserName(), type3Msg.getNTLMHash(), PasswordEncryptor.NTLM1);
try
{
// Run the second stage of the passthru authentication
m_authComponent.authenticate(authToken);
authenticated = true;
}
catch (AuthenticationException ex)
{
// Debug
if ( logger.isDebugEnabled())
logger.debug("Authentication failed, " + ex.getMessage());
}
finally
{
// Clear the authentication token from the NTLM details
ntlmDetails.setAuthenticationToken(null);
}
}
// Check if the user has been authenticated, if so then setup the user environment
if ( authenticated == true)
{
UserTransaction tx = m_transactionService.getUserTransaction();
try
@@ -809,8 +269,8 @@ public class NTLMAuthenticationFilter implements Filter
// Get user details for the authenticated user
m_authComponent.setCurrentUser(userName.toLowerCase());
// The user name used may be a different case to the NTLM supplied user name, read the current
// user and use that name
// The user name used may be a different case to the NTLM supplied user name,
// read the current user and use that name
userName = m_authComponent.getCurrentUserName();
// Setup User object and Home space ID etc.
@@ -819,108 +279,60 @@ public class NTLMAuthenticationFilter implements Filter
user = new WebDAVUser(userName, currentTicket, personNodeRef);
NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER);
user.setHomeNode(homeSpaceRef);
((WebDAVUser)user).setHomeNode(homeSpaceRef);
// commit
tx.commit();
tx = null;
}
catch ( Throwable ex) {
if ( logger.isDebugEnabled())
logger.debug(ex);
}
finally {
// Rollback/commit the transaction if still valid
if ( tx != null) {
catch (Throwable ex)
{
try
{
// Commit or rollback the transaction
if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK ||
tx.getStatus() == Status.STATUS_ROLLEDBACK ||
tx.getStatus() == Status.STATUS_ROLLING_BACK)
{
// Transaction is marked for rollback
tx.rollback();
}
else
catch (Exception ex2)
{
// Commit the transaction
tx.commit();
logger.error("Failed to rollback transaction", ex2);
}
}
catch (Throwable ex) {
if ( logger.isDebugEnabled())
logger.debug(ex);
}
}
}
// Store the user
httpSess.setAttribute(AUTHENTICATION_USER, user);
// Update the NTLM logon details in the session
if ( ntlmDetails == null)
if (ex instanceof RuntimeException)
{
// No cached NTLM details
ntlmDetails = new NTLMLogonDetails( userName, workstation, domain, false, m_srvName);
httpSess.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails);
// Debug
if ( logger.isDebugEnabled())
logger.debug("No cached NTLM details, created");
throw (RuntimeException)ex;
}
else if (ex instanceof IOException)
{
throw (IOException)ex;
}
else if (ex instanceof ServletException)
{
throw (ServletException)ex;
}
else
{
// Update the cached NTLM details
ntlmDetails.setDetails(userName, workstation, domain, false, m_srvName);
ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash());
// Debug
if ( logger.isDebugEnabled())
logger.debug("Updated cached NTLM details");
throw new RuntimeException("Authentication setup failed", ex);
}
}
// Debug
// Store the user on the session
session.setAttribute(AUTHENTICATION_USER, user);
if ( logger.isDebugEnabled())
logger.debug("User logged on via NTLM, " + ntlmDetails);
// Allow the user to access the requested page
chain.doFilter( req, resp);
return;
return user;
}
else
/* (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
{
// Remove any existing session and NTLM details from the session
httpSess.removeAttribute(NTLM_AUTH_SESSION);
httpSess.removeAttribute(NTLM_AUTH_DETAILS);
// Force the logon to start again
resp.setHeader("WWW-Authenticate", "NTLM");
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
resp.flushBuffer();
return;
}
// no futher processing to do, allow to complete
return true;
}
/* (non-Javadoc)
* @see org.alfresco.repo.webdav.auth.BaseNTLMAuthenticationFilter#getLogger()
*/
@Override
final protected Log getLogger()
{
return logger;
}
}

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.webdav.auth;
import java.io.Serializable;
import org.alfresco.repo.SessionUser;
import org.alfresco.service.cmr.repository.NodeRef;
/**
@@ -36,7 +37,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
*
* @author GKSpencer
*/
public class WebDAVUser implements Serializable
public class WebDAVUser implements SessionUser
{
private static final long serialVersionUID = -6948146071131901345L;