From ef391a2c97ab980accb5c554e849f7cbce974246 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Fri, 12 Jun 2009 10:05:49 +0000 Subject: [PATCH] Rework of CHK-7655 (14592): Sharepoint authentication handlers moved to authentication subsystems so that NTLM enablement is automatic. - No need to reconfigure anything if you install the sharepoint amp and switch on NTLM SSO git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14681 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfrescoNtlm/ntlm-filter-context.xml | 22 + .../passthru/ntlm-filter-context.xml | 22 + .../web-client-application-context.xml | 25 + .../auth/AbstractAuthenticationHandler.java | 84 +++ .../auth/AuthenticationHandler.java | 73 +++ .../auth/BasicAuthenticationHandler.java | 102 +++ .../web/sharepoint/auth/SiteMemberMapper.java | 52 ++ .../auth/SiteMemberMappingException.java | 91 +++ .../auth/ntlm/NtlmAuthenticationHandler.java | 591 ++++++++++++++++++ 9 files changed, 1062 insertions(+) create mode 100644 source/java/org/alfresco/web/sharepoint/auth/AbstractAuthenticationHandler.java create mode 100644 source/java/org/alfresco/web/sharepoint/auth/AuthenticationHandler.java create mode 100644 source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java create mode 100644 source/java/org/alfresco/web/sharepoint/auth/SiteMemberMapper.java create mode 100644 source/java/org/alfresco/web/sharepoint/auth/SiteMemberMappingException.java create mode 100644 source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java diff --git a/config/alfresco/subsystems/Authentication/alfrescoNtlm/ntlm-filter-context.xml b/config/alfresco/subsystems/Authentication/alfrescoNtlm/ntlm-filter-context.xml index d2dba7c673..14b74da0eb 100644 --- a/config/alfresco/subsystems/Authentication/alfrescoNtlm/ntlm-filter-context.xml +++ b/config/alfresco/subsystems/Authentication/alfrescoNtlm/ntlm-filter-context.xml @@ -97,4 +97,26 @@ ${ntlm.authentication.mapUnknownUserToGuest} + + + + ${ntlm.authentication.sso.enabled} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/Authentication/passthru/ntlm-filter-context.xml b/config/alfresco/subsystems/Authentication/passthru/ntlm-filter-context.xml index d2dba7c673..14b74da0eb 100644 --- a/config/alfresco/subsystems/Authentication/passthru/ntlm-filter-context.xml +++ b/config/alfresco/subsystems/Authentication/passthru/ntlm-filter-context.xml @@ -97,4 +97,26 @@ ${ntlm.authentication.mapUnknownUserToGuest} + + + + ${ntlm.authentication.sso.enabled} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/web-client-application-context.xml b/config/alfresco/web-client-application-context.xml index 75b67afa7c..2dd8f5d7f1 100644 --- a/config/alfresco/web-client-application-context.xml +++ b/config/alfresco/web-client-application-context.xml @@ -262,4 +262,29 @@ + + + + + + + org.alfresco.web.sharepoint.auth.AuthenticationHandler + + + + sharepointAuthenticationHandler + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/web/sharepoint/auth/AbstractAuthenticationHandler.java b/source/java/org/alfresco/web/sharepoint/auth/AbstractAuthenticationHandler.java new file mode 100644 index 0000000000..a5164f0e36 --- /dev/null +++ b/source/java/org/alfresco/web/sharepoint/auth/AbstractAuthenticationHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2005-2009 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 received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.sharepoint.auth; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.management.subsystems.ActivateableBean; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

Abstract implementation of web authentication.

+ * + * @author PavelYur + * + */ +public abstract class AbstractAuthenticationHandler implements AuthenticationHandler, ActivateableBean +{ + protected Log logger = LogFactory.getLog(getClass()); + protected AuthenticationService authenticationService; + protected PersonService personService; + private boolean isActive = true; + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setActive(boolean isActive) + { + this.isActive = isActive; + } + + public boolean isActive() + { + return this.isActive; + } + + /** + * Returns the value of 'WWW-Authenticate' http header that determine what type of authentication to use by + * client. + * + * @return value + */ + public abstract String getWWWAuthenticate(); + + public void forceClientToPromptLogonDetails(HttpServletResponse response) + { + if (logger.isDebugEnabled()) + logger.debug("Force the client to prompt for logon details"); + + response.setHeader(HEADER_WWW_AUTHENTICATE, getWWWAuthenticate()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/sharepoint/auth/AuthenticationHandler.java b/source/java/org/alfresco/web/sharepoint/auth/AuthenticationHandler.java new file mode 100644 index 0000000000..36ac36e223 --- /dev/null +++ b/source/java/org/alfresco/web/sharepoint/auth/AuthenticationHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005-2009 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 received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.sharepoint.auth; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.SessionUser; + +/** + * Sharepoint authentication plugin API + * + * @author PavelYur + */ +public interface AuthenticationHandler +{ + public final static String HEADER_AUTHORIZATION = "Authorization"; + + public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + public final static String NTLM_START = "NTLM"; + + public final static String BASIC_START = "BASIC"; + + public final static String USER_SESSION_ATTRIBUTE = "_vtiAuthTicket"; + + /** + * Authenticate user based on information in http request such as Authorization header or else. + * + * @param request + * http request + * @param response + * http response + * @param alfrescoContext + * deployment context of alfresco application + * @param mapper + * an object capable of determining which users are site members + * @return SessionUser information about currently loged in user or null. + */ + public SessionUser authenticateRequest(HttpServletRequest request, HttpServletResponse response, + SiteMemberMapper mapper, String alfrescoContext); + + /** + * Send to user response with http status 401 + * + * @param response + * http response + */ + public void forceClientToPromptLogonDetails(HttpServletResponse response); + +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java b/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java new file mode 100644 index 0000000000..4886f38f8c --- /dev/null +++ b/source/java/org/alfresco/web/sharepoint/auth/BasicAuthenticationHandler.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.sharepoint.auth; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.alfresco.repo.SessionUser; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.web.bean.repository.User; +import org.apache.commons.codec.binary.Base64; + +/** + *

BASIC web authentication implementation.

+ * + * @author PavelYur + * + */ +public class BasicAuthenticationHandler extends AbstractAuthenticationHandler +{ + /* (non-Javadoc) + * @see org.alfresco.web.vti.auth.AuthenticationHandler#authenticateRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.alfresco.web.vti.auth.SiteMemberMapper, java.lang.String) + */ + public SessionUser authenticateRequest(HttpServletRequest request, HttpServletResponse response, + SiteMemberMapper mapper, String alfrescoContext) + { + SessionUser user = null; + + String authHdr = request.getHeader(HEADER_AUTHORIZATION); + HttpSession session = request.getSession(); + + if (authHdr != null && authHdr.length() > 5 && authHdr.substring(0, 5).equalsIgnoreCase(BASIC_START)) + { + String basicAuth = new String(Base64.decodeBase64(authHdr.substring(5).getBytes())); + String username = null; + String password = null; + + int pos = basicAuth.indexOf(":"); + if (pos != -1) + { + username = basicAuth.substring(0, pos); + password = basicAuth.substring(pos + 1); + } + else + { + username = basicAuth; + password = ""; + } + + try + { + if (logger.isDebugEnabled()) + logger.debug("Authenticate the user '" + username + "'"); + + authenticationService.authenticate(username, password.toCharArray()); + + if (mapper.isSiteMember(request, alfrescoContext, username)) + { + user = new User(username, authenticationService.getCurrentTicket(), personService.getPerson(username)); + if (session != null) + session.setAttribute(USER_SESSION_ATTRIBUTE, user); + } + } + catch (AuthenticationException ex) + { + // Do nothing, user object will be null + } + } + + return user; + } + + + @Override + public String getWWWAuthenticate() + { + return "BASIC realm=\"Alfresco Server\""; + } +} diff --git a/source/java/org/alfresco/web/sharepoint/auth/SiteMemberMapper.java b/source/java/org/alfresco/web/sharepoint/auth/SiteMemberMapper.java new file mode 100644 index 0000000000..1ac00fd162 --- /dev/null +++ b/source/java/org/alfresco/web/sharepoint/auth/SiteMemberMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2009 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 received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.sharepoint.auth; + +import javax.servlet.http.HttpServletRequest; + +/** + * An object capable of answering whether a particular user is a member of the site indicated by the request URL. + * + * @author dward + */ +public interface SiteMemberMapper +{ + + /** + * Determines whether a particular user is a member of the site indicated by the request URI. + * + * @param request + * the request + * @param alfrescoContext + * the context path to strip from the request URI + * @param userName + * the user name + * @return true if the user is a member + * @throws SiteMemberMappingException + * on error + */ + boolean isSiteMember(HttpServletRequest request, String alfrescoContext, String userName) + throws SiteMemberMappingException; +} diff --git a/source/java/org/alfresco/web/sharepoint/auth/SiteMemberMappingException.java b/source/java/org/alfresco/web/sharepoint/auth/SiteMemberMappingException.java new file mode 100644 index 0000000000..f0c6bb3799 --- /dev/null +++ b/source/java/org/alfresco/web/sharepoint/auth/SiteMemberMappingException.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2009 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 received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.sharepoint.auth; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * An exception thrown by a {@link SiteMemberMapper}. + * + * @author dward + */ +public class SiteMemberMappingException extends AlfrescoRuntimeException +{ + + private static final long serialVersionUID = -7235067946629381543L; + + /** + * Constructs a SiteMemberMappingException. + * + * @param msgId + * the message id + */ + public SiteMemberMappingException(String msgId) + { + super(msgId); + } + + /** + * Constructs a SiteMemberMappingException. + * + * @param msgId + * the message id + * @param msgParams + * the message parameters + */ + public SiteMemberMappingException(String msgId, Object[] msgParams) + { + super(msgId, msgParams); + } + + /** + * Constructs a SiteMemberMappingException. + * + * @param msgId + * the message id + * @param cause + * the cause + */ + public SiteMemberMappingException(String msgId, Throwable cause) + { + super(msgId, cause); + } + + /** + * Constructs a SiteMemberMappingException. + * + * @param msgId + * the message id + * @param msgParams + * the message parameters + * @param cause + * the cause + */ + public SiteMemberMappingException(String msgId, Object[] msgParams, Throwable cause) + { + super(msgId, msgParams, cause); + } + +} diff --git a/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java b/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java new file mode 100644 index 0000000000..f86550a110 --- /dev/null +++ b/source/java/org/alfresco/web/sharepoint/auth/ntlm/NtlmAuthenticationHandler.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.web.sharepoint.auth.ntlm; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +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.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.SessionUser; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MD4PasswordEncoder; +import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.sharepoint.auth.AbstractAuthenticationHandler; +import org.alfresco.web.sharepoint.auth.SiteMemberMapper; +import org.alfresco.web.sharepoint.auth.SiteMemberMappingException; +import org.apache.commons.codec.binary.Base64; + +/** + *

+ * NTLM SSO web authentication implementation. + *

+ */ +public class NtlmAuthenticationHandler extends AbstractAuthenticationHandler +{ + // NTLM authentication session object names + private static final String NTLM_AUTH_DETAILS = "_alfNTLMDetails"; + + private MD4PasswordEncoder md4Encoder = new MD4PasswordEncoderImpl(); + private PasswordEncryptor encryptor = new PasswordEncryptor(); + private Random random = new Random(System.currentTimeMillis()); + + private NLTMAuthenticator authenticationComponent; + private TransactionService transactionService; + private NodeService nodeService; + private static final int ntlmFlags = NTLM.Flag56Bit + NTLM.FlagLanManKey + NTLM.FlagNegotiateNTLM + + NTLM.FlagNegotiateOEM + NTLM.FlagNegotiateUnicode; + + public void setAuthenticationComponent(NLTMAuthenticator authenticationComponent) + { + this.authenticationComponent = authenticationComponent; + } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public SessionUser authenticateRequest(HttpServletRequest request, HttpServletResponse response, + SiteMemberMapper mapper, String alfrescoContext) + { + if (logger.isDebugEnabled()) + { + logger.debug("Start NTLM authentication for request: " + request.getRequestURI()); + } + + HttpSession session = request.getSession(); + SessionUser user = (SessionUser) session.getAttribute(USER_SESSION_ATTRIBUTE); + + String authHdr = request.getHeader(HEADER_AUTHORIZATION); + + boolean needToAuthenticate = false; + + if (authHdr != null && authHdr.startsWith(NTLM_START)) + { + needToAuthenticate = true; + } + + if (user != null && needToAuthenticate == false) + { + try + { + authenticationService.validate(user.getTicket()); + needToAuthenticate = false; + } + catch (AuthenticationException e) + { + session.removeAttribute(USER_SESSION_ATTRIBUTE); + needToAuthenticate = true; + } + } + + if (needToAuthenticate == false && user != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("NTLM header wasn't present. Authenticated by user from session. Username: " + + user.getUserName()); + } + return user; + } + + if (authHdr == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("NTLM header wasn't present. No user was found in session. Return 401 status."); + } + removeNtlmLogonDetailsFromSession(request); + forceClientToPromptLogonDetails(response); + return null; + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("NTLM header present in request."); + } + // 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) + { + Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts); + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Start process type 1 message."); + } + processType1(type1Msg, request, response, session); + user = null; + if (logger.isDebugEnabled()) + { + logger.debug("Finish process type 1 message."); + } + } + catch (Exception e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Process type 1 message fail with error: " + e.getMessage()); + } + session.removeAttribute(USER_SESSION_ATTRIBUTE); + removeNtlmLogonDetailsFromSession(request); + return null; + } + + } + else if (ntlmTyp == NTLM.Type3) + { + Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts); + + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Start process message type 3."); + } + user = processType3(type3Msg, mapper, request, response, session, alfrescoContext); + if (logger.isDebugEnabled()) + { + logger.debug("Finish process message type 3."); + } + } + catch (SiteMemberMappingException e) + { + throw e; + } + catch (Exception e) + { + if (user != null) + { + try + { + authenticationService.validate(user.getTicket()); + return user; + } + catch (AuthenticationException ae) + { + } + } + if (logger.isDebugEnabled()) + { + logger.debug("Process message type 3 fail with message: " + e.getMessage()); + } + session.removeAttribute(USER_SESSION_ATTRIBUTE); + removeNtlmLogonDetailsFromSession(request); + return null; + } + } + + return user; + } + } + + @Override + public String getWWWAuthenticate() + { + return NTLM_START; + } + + private void processType1(Type1NTLMMessage type1Msg, HttpServletRequest request, HttpServletResponse response, + HttpSession session) throws IOException + { + removeNtlmLogonDetailsFromSession(request); + + NTLMLogonDetails ntlmDetails = new NTLMLogonDetails(); + + // Set the 8 byte challenge for the new logon request + byte[] challenge = null; + + // Generate a random 8 byte challenge + challenge = new byte[8]; + DataPacker.putIntelLong(random.nextLong(), challenge, 0); + + // Get the flags from the client request and mask out unsupported features + int flags = type1Msg.getFlags() & ntlmFlags; + + // Build a type2 message to send back to the client, containing the challenge + List tList = new ArrayList(); + String srvName = getServerName(); + tList.add(new TargetInfo(NTLM.TargetServer, srvName)); + + Type2NTLMMessage type2Msg = new Type2NTLMMessage(); + type2Msg.buildType2(flags, srvName, challenge, null, tList); + + // Store the NTLM logon details, cache the type2 message, and token if using passthru + ntlmDetails.setType2Message(type2Msg); + ntlmDetails.setAuthenticationToken(null); + + putNtlmLogonDetailsToSession(request, ntlmDetails); + + // Send back a request for NTLM authentication + byte[] type2Bytes = type2Msg.getBytes(); + String ntlmBlob = "NTLM " + new String(Base64.encodeBase64(type2Bytes)); + + response.setHeader(HEADER_WWW_AUTHENTICATE, ntlmBlob); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.flushBuffer(); + response.getOutputStream().close(); + + } + + private SessionUser processType3(Type3NTLMMessage type3Msg, SiteMemberMapper callback, HttpServletRequest request, + HttpServletResponse response, HttpSession session, String alfrescoContext) throws IOException, + ServletException + { + + // Get the existing NTLM details + NTLMLogonDetails ntlmDetails = null; + SessionUser user = null; + + if (session != null) + { + ntlmDetails = getNtlmLogonDetailsFromSession(request); + user = (SessionUser) session.getAttribute(USER_SESSION_ATTRIBUTE); + } + + // Get the NTLM logon details + String userName = type3Msg.getUserName(); + String workstation = type3Msg.getWorkstation(); + String domain = type3Msg.getDomain(); + + boolean authenticated = false; + + // 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 + { + authenticated = false; + } + + // Check if the user has been authenticated, if so then setup the user environment + if (authenticated == true && callback.isSiteMember(request, alfrescoContext, userName)) + { + String uri = request.getRequestURI(); + + if (request.getMethod().equals("POST") && !uri.endsWith(".asmx")) + { + response.setHeader("Connection", "Close"); + response.setContentType("application/x-vermeer-rpc"); + } + + if (user == null) + { + user = createUserEnvironment(session, userName); + } + else + { + // user already exists - revalidate ticket to authenticate the current user thread + try + { + authenticationService.validate(user.getTicket()); + } + catch (AuthenticationException ex) + { + session.removeAttribute(USER_SESSION_ATTRIBUTE); + removeNtlmLogonDetailsFromSession(request); + return null; + } + } + + // Update the NTLM logon details in the session + String srvName = getServerName(); + if (ntlmDetails == null) + { + // No cached NTLM details + ntlmDetails = new NTLMLogonDetails(userName, workstation, domain, false, srvName); + putNtlmLogonDetailsToSession(request, ntlmDetails); + } + else + { + // Update the cached NTLM details + ntlmDetails.setDetails(userName, workstation, domain, false, srvName); + putNtlmLogonDetailsToSession(request, ntlmDetails); + } + } + else + { + removeNtlmLogonDetailsFromSession(request); + session.removeAttribute(USER_SESSION_ATTRIBUTE); + return null; + } + return user; + } + + /* + * returns server name + */ + private String getServerName() + { + return "Alfresco Server"; + } + + /* + * Create the SessionUser object that represent currently authenticated user. + */ + private SessionUser createUserEnvironment(HttpSession session, final String userName) throws IOException, + ServletException + { + SessionUser user = null; + + UserTransaction tx = transactionService.getUserTransaction(); + + try + { + tx.begin(); + + RunAsWork getUserNodeRefRunAsWork = new RunAsWork() + { + public NodeRef doWork() throws Exception + { + + return personService.getPerson(userName); + } + }; + + NodeRef personNodeRef = AuthenticationUtil.runAs(getUserNodeRefRunAsWork, + AuthenticationUtil.SYSTEM_USER_NAME); + + // Use the system user context to do the user lookup + RunAsWork getUserNameRunAsWork = new RunAsWork() + { + public String doWork() throws Exception + { + final NodeRef personNodeRef = personService.getPerson(userName); + return (String) nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + } + }; + String username = AuthenticationUtil.runAs(getUserNameRunAsWork, AuthenticationUtil.SYSTEM_USER_NAME); + + authenticationComponent.setCurrentUser(userName); + String currentTicket = authenticationService.getCurrentTicket(); + + // Create the user object to be stored in the session + user = new User(username, currentTicket, personNodeRef); + + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Exception err) + { + logger.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(USER_SESSION_ATTRIBUTE, user); + + return user; + } + + /* + * returns the hash of password + */ + protected String getMD4Hash(String userName) + { + String md4hash = null; + + // Wrap the auth component calls in a transaction + UserTransaction tx = transactionService.getUserTransaction(); + try + { + tx.begin(); + + // Get the stored MD4 hashed password for the user, or null if the user does not exist + md4hash = authenticationComponent.getMD4HashedPassword(userName); + + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Exception e) + { + } + } + + return md4hash; + } + + /* + * Validate local hash for user password and hash that was sent by client + */ + private boolean validateLocalHashedPassword(Type3NTLMMessage type3Msg, NTLMLogonDetails ntlmDetails, + boolean authenticated, String md4hash) + { + if (ntlmDetails == null || ntlmDetails.getType2Message() == null) + { + return false; + } + + authenticated = checkNTLMv1(md4hash, ntlmDetails.getChallengeKey(), type3Msg, false); + + return authenticated; + } + + private final boolean checkNTLMv1(String md4hash, byte[] challenge, Type3NTLMMessage type3Msg, boolean checkLMHash) + { + // Generate the local encrypted password using the challenge that was sent to the client + byte[] p21 = new byte[21]; + byte[] md4byts = 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 = encryptor.doNTLM1Encryption(p21, challenge); + } + catch (NoSuchAlgorithmException ex) + { + } + + // Validate the password + byte[] clientHash = checkLMHash ? type3Msg.getLMHash() : 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; + } + + @SuppressWarnings("unchecked") + private void putNtlmLogonDetailsToSession(HttpServletRequest request, NTLMLogonDetails details) + { + Object detailsMap = request.getSession().getAttribute(NTLM_AUTH_DETAILS); + + if (detailsMap != null) + { + ((Map) detailsMap).put(request.getRequestURI(), details); + return; + } + else + { + Map newMap = new HashMap(); + newMap.put(request.getRequestURI(), details); + request.getSession().setAttribute(NTLM_AUTH_DETAILS, newMap); + } + } + + @SuppressWarnings("unchecked") + private NTLMLogonDetails getNtlmLogonDetailsFromSession(HttpServletRequest request) + { + Object detailsMap = request.getSession().getAttribute(NTLM_AUTH_DETAILS); + if (detailsMap != null) + { + return ((Map) detailsMap).get(request.getRequestURI()); + } + return null; + } + + @SuppressWarnings("unchecked") + private void removeNtlmLogonDetailsFromSession(HttpServletRequest request) + { + Object detailsMap = request.getSession().getAttribute(NTLM_AUTH_DETAILS); + if (detailsMap != null) + { + ((Map) detailsMap).remove(request.getRequestURI()); + } + } + +} \ No newline at end of file