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