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 @@
+
+
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 + * @returntrue
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