001/* 002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nuxeo - initial API and implementation 016 * 017 * $Id: JOOoConvertPluginImpl.java 18651 2007-05-13 20:28:53Z sfermigier $ 018 */ 019 020package org.nuxeo.ecm.platform.ui.web.auth.ntlm; 021 022import java.io.IOException; 023import java.util.List; 024import java.util.Map; 025 026import javax.servlet.ServletException; 027import javax.servlet.http.HttpServletRequest; 028import javax.servlet.http.HttpServletResponse; 029import javax.servlet.http.HttpSession; 030 031import jcifs.Config; 032import jcifs.UniAddress; 033import jcifs.http.NtlmSsp; 034 035import jcifs.smb.NtlmChallenge; 036import jcifs.smb.NtlmPasswordAuthentication; 037import jcifs.smb.SmbAuthException; 038import jcifs.smb.SmbSession; 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo; 042import org.nuxeo.ecm.platform.ui.web.auth.interfaces.NuxeoAuthenticationPlugin; 043 044import static jcifs.smb.NtStatus.NT_STATUS_ACCESS_VIOLATION; 045 046public class NTLMAuthenticator implements NuxeoAuthenticationPlugin { 047 048 private static final String JCIFS_PREFIX = "jcifs."; 049 050 public static final String JCIFS_NETBIOS_CACHE_POLICY = "jcifs.netbios.cachePolicy"; 051 052 public static final String JCIFS_SMB_CLIENT_SO_TIMEOUT = "jcifs.smb.client.soTimeout"; 053 054 public static final String JCIFS_HTTP_LOAD_BALANCE = "jcifs.http.loadBalance"; 055 056 public static final String JCIFS_HTTP_DOMAIN_CONTROLLER = "jcifs.http.domainController"; 057 058 public static final String JCIFS_SMB_CLIENT_DOMAIN = "jcifs.smb.client.domain"; 059 060 public static final boolean FORCE_SESSION_CREATION = true; 061 062 public static final String NTLM_HTTP_AUTH_SESSION_KEY = "NtlmHttpAuth"; 063 064 public static final String NTLM_HTTP_CHAL_SESSION_KEY = "NtlmHttpChal"; 065 066 protected static String defaultDomain; 067 068 protected static String domainController; 069 070 protected static boolean loadBalance; 071 072 private static final Log log = LogFactory.getLog(NTLMAuthenticator.class); 073 074 public List<String> getUnAuthenticatedURLPrefix() { 075 return null; 076 } 077 078 public Boolean handleLoginPrompt(HttpServletRequest httpRequest, HttpServletResponse httpResponse, String baseURL) { 079 080 log.debug("Handle NTLM login prompt"); 081 NtlmPasswordAuthentication ntlm = null; 082 HttpSession httpSession = httpRequest.getSession(FORCE_SESSION_CREATION); 083 084 if (httpSession != null) { 085 ntlm = (NtlmPasswordAuthentication) httpSession.getAttribute(NTLM_HTTP_AUTH_SESSION_KEY); 086 } 087 088 if (httpSession == null || ntlm == null) { 089 log.debug("Sending NTLM Challenge/Response request to browser"); 090 httpResponse.setHeader("WWW-Authenticate", "NTLM"); 091 httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 092 httpResponse.setContentLength(0); 093 try { 094 httpResponse.flushBuffer(); 095 } catch (IOException e) { 096 log.error("Error while flushing buffer:" + e.getMessage()); 097 e.printStackTrace(); 098 } 099 return true; 100 } else { 101 log.debug("No NTLM Prompt done since NTLM Auth was found :" + ntlm.getUsername()); 102 return false; 103 } 104 } 105 106 public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest, 107 HttpServletResponse httpResponse) { 108 109 log.debug("NTML handleRetrieveIdentity"); 110 NtlmPasswordAuthentication ntlm; 111 112 try { 113 ntlm = negotiate(httpRequest, httpResponse, false); 114 } catch (IOException | ServletException e) { 115 log.error("NTLM negotiation failed : " + e.getMessage(), e); 116 return null; 117 } 118 119 if (ntlm == null) { 120 log.debug("Negotiation returned a null NTLM token"); 121 return null; 122 } else { 123 log.debug("Negotiation succeed and returned a NTLM token, creating UserIdentificationInfo"); 124 String userName = ntlm.getUsername(); 125 log.debug("ntlm.getUsername() = " + userName); 126 if (userName.startsWith(ntlm.getDomain())) { 127 userName = userName.replace(ntlm.getDomain() + "/", ""); 128 } 129 log.debug("userName = " + userName); 130 String password = ntlm.getPassword(); 131 if (password == null || "".equals(password)) { 132 // we don't get the NTLM password, so we have to trust NTLM auth 133 UserIdentificationInfo userInfo = new UserIdentificationInfo(ntlm.getUsername(), "ITrustNTLM"); 134 userInfo.setLoginPluginName("Trusting_LM"); 135 return userInfo; 136 } else { 137 return new UserIdentificationInfo(ntlm.getUsername(), ntlm.getPassword()); 138 } 139 } 140 } 141 142 public void initPlugin(Map<String, String> parameters) { 143 144 Config.setProperty(JCIFS_SMB_CLIENT_SO_TIMEOUT, "300000"); 145 Config.setProperty(JCIFS_NETBIOS_CACHE_POLICY, "1200"); 146 147 // init CIFS from parameters 148 for (String name : parameters.keySet()) { 149 if (name.startsWith(JCIFS_PREFIX)) { 150 Config.setProperty(name, parameters.get(name)); 151 } 152 } 153 154 // get params from CIFS config 155 defaultDomain = Config.getProperty(JCIFS_SMB_CLIENT_DOMAIN); 156 domainController = Config.getProperty(JCIFS_HTTP_DOMAIN_CONTROLLER); 157 if (domainController == null) { 158 domainController = defaultDomain; 159 loadBalance = Config.getBoolean(JCIFS_HTTP_LOAD_BALANCE, true); 160 } 161 } 162 163 public Boolean needLoginPrompt(HttpServletRequest httpRequest) { 164 String useragent = httpRequest.getHeader("User-Agent").toLowerCase(); 165 166 // only prompt on windows platform 167 168 if (!useragent.contains("windows")) { 169 log.debug("No NTLM LoginPrompt : User does not use Win32"); 170 return false; 171 } 172 173 log.debug("NTLM LoginPrompt Needed"); 174 return true; 175 } 176 177 public static NtlmPasswordAuthentication negotiate(HttpServletRequest req, HttpServletResponse resp, 178 boolean skipAuthentication) throws IOException, ServletException { 179 log.debug("NTLM negotiation starts"); 180 181 String msg = req.getHeader("Authorization"); 182 183 log.debug("NTLM negotiation header = " + msg); 184 NtlmPasswordAuthentication ntlm; 185 if (msg != null && msg.startsWith("NTLM ")) { 186 HttpSession ssn = req.getSession(); 187 byte[] challenge; 188 189 UniAddress dc; 190 if (loadBalance) { 191 NtlmChallenge chal = (NtlmChallenge) ssn.getAttribute(NTLM_HTTP_CHAL_SESSION_KEY); 192 if (chal == null) { 193 chal = SmbSession.getChallengeForDomain(); 194 ssn.setAttribute(NTLM_HTTP_CHAL_SESSION_KEY, chal); 195 } 196 dc = chal.dc; 197 challenge = chal.challenge; 198 } else { 199 dc = UniAddress.getByName(domainController, true); 200 dc = UniAddress.getByName(dc.getHostAddress(), true); 201 challenge = SmbSession.getChallenge(dc); 202 } 203 204 ntlm = NtlmSsp.authenticate(req, resp, challenge); 205 if (ntlm == null) { 206 log.debug("NtlmSsp.authenticate returned null"); 207 return null; 208 } 209 210 log.debug("NtlmSsp.authenticate succeed"); 211 log.debug("Domain controller is " + dc.getHostName()); 212 if (ntlm.getDomain() != null) { 213 log.debug("NtlmSsp.authenticate => domain = " + ntlm.getDomain()); 214 } else { 215 log.debug("NtlmSsp.authenticate => null domain"); 216 } 217 if (ntlm.getUsername() != null) { 218 log.debug("NtlmSsp.authenticate => userName = " + ntlm.getUsername()); 219 } else { 220 log.debug("NtlmSsp.authenticate => userName = null"); 221 } 222 if (ntlm.getPassword() != null) { 223 log.debug("NtlmSsp.authenticate => password = " + ntlm.getPassword()); 224 } else { 225 log.debug("NtlmSsp.authenticate => password = null"); 226 } 227 228 /* negotiation complete, remove the challenge object */ 229 ssn.removeAttribute(NTLM_HTTP_CHAL_SESSION_KEY); 230 if (!skipAuthentication) { 231 try { 232 log.debug("Trying to logon NTLM session on dc " + dc.toString()); 233 SmbSession.logon(dc, ntlm); 234 log.debug(ntlm + " successfully authenticated against " + dc); 235 236 } catch (SmbAuthException sae) { 237 238 log.error(ntlm.getName() + ": 0x" + jcifs.util.Hexdump.toHexString(sae.getNtStatus(), 8) + ": " 239 + sae); 240 241 if (sae.getNtStatus() == NT_STATUS_ACCESS_VIOLATION) { 242 /* 243 * Server challenge no longer valid for externally supplied password hashes. 244 */ 245 ssn = req.getSession(false); 246 if (ssn != null) { 247 ssn.removeAttribute(NTLM_HTTP_AUTH_SESSION_KEY); 248 } 249 } 250 resp.setHeader("WWW-Authenticate", "NTLM"); 251 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 252 resp.setContentLength(0); 253 resp.flushBuffer(); 254 return null; 255 } 256 req.getSession().setAttribute(NTLM_HTTP_AUTH_SESSION_KEY, ntlm); 257 } 258 } else { 259 log.debug("NTLM negotiation header is null"); 260 return null; 261 } 262 return ntlm; 263 } 264 265}