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(), e); 099 } 100 return true; 101 } else { 102 log.debug("No NTLM Prompt done since NTLM Auth was found :" + ntlm.getUsername()); 103 return false; 104 } 105 } 106 107 public UserIdentificationInfo handleRetrieveIdentity(HttpServletRequest httpRequest, 108 HttpServletResponse httpResponse) { 109 110 log.debug("NTML handleRetrieveIdentity"); 111 NtlmPasswordAuthentication ntlm; 112 113 try { 114 ntlm = negotiate(httpRequest, httpResponse, false); 115 } catch (IOException | ServletException e) { 116 log.error("NTLM negotiation failed : " + e.getMessage(), e); 117 return null; 118 } 119 120 if (ntlm == null) { 121 log.debug("Negotiation returned a null NTLM token"); 122 return null; 123 } else { 124 log.debug("Negotiation succeed and returned a NTLM token, creating UserIdentificationInfo"); 125 String userName = ntlm.getUsername(); 126 log.debug("ntlm.getUsername() = " + userName); 127 if (userName.startsWith(ntlm.getDomain())) { 128 userName = userName.replace(ntlm.getDomain() + "/", ""); 129 } 130 log.debug("userName = " + userName); 131 String password = ntlm.getPassword(); 132 if (password == null || "".equals(password)) { 133 // we don't get the NTLM password, so we have to trust NTLM auth 134 UserIdentificationInfo userInfo = new UserIdentificationInfo(ntlm.getUsername(), "ITrustNTLM"); 135 userInfo.setLoginPluginName("Trusting_LM"); 136 return userInfo; 137 } else { 138 return new UserIdentificationInfo(ntlm.getUsername(), ntlm.getPassword()); 139 } 140 } 141 } 142 143 public void initPlugin(Map<String, String> parameters) { 144 145 Config.setProperty(JCIFS_SMB_CLIENT_SO_TIMEOUT, "300000"); 146 Config.setProperty(JCIFS_NETBIOS_CACHE_POLICY, "1200"); 147 148 // init CIFS from parameters 149 for (String name : parameters.keySet()) { 150 if (name.startsWith(JCIFS_PREFIX)) { 151 Config.setProperty(name, parameters.get(name)); 152 } 153 } 154 155 // get params from CIFS config 156 defaultDomain = Config.getProperty(JCIFS_SMB_CLIENT_DOMAIN); 157 domainController = Config.getProperty(JCIFS_HTTP_DOMAIN_CONTROLLER); 158 if (domainController == null) { 159 domainController = defaultDomain; 160 loadBalance = Config.getBoolean(JCIFS_HTTP_LOAD_BALANCE, true); 161 } 162 } 163 164 public Boolean needLoginPrompt(HttpServletRequest httpRequest) { 165 String useragent = httpRequest.getHeader("User-Agent").toLowerCase(); 166 167 // only prompt on windows platform 168 169 if (!useragent.contains("windows")) { 170 log.debug("No NTLM LoginPrompt : User does not use Win32"); 171 return false; 172 } 173 174 log.debug("NTLM LoginPrompt Needed"); 175 return true; 176 } 177 178 public static NtlmPasswordAuthentication negotiate(HttpServletRequest req, HttpServletResponse resp, 179 boolean skipAuthentication) throws IOException, ServletException { 180 log.debug("NTLM negotiation starts"); 181 182 String msg = req.getHeader("Authorization"); 183 184 log.debug("NTLM negotiation header = " + msg); 185 NtlmPasswordAuthentication ntlm; 186 if (msg != null && msg.startsWith("NTLM ")) { 187 HttpSession ssn = req.getSession(); 188 byte[] challenge; 189 190 UniAddress dc; 191 if (loadBalance) { 192 NtlmChallenge chal = (NtlmChallenge) ssn.getAttribute(NTLM_HTTP_CHAL_SESSION_KEY); 193 if (chal == null) { 194 chal = SmbSession.getChallengeForDomain(); 195 ssn.setAttribute(NTLM_HTTP_CHAL_SESSION_KEY, chal); 196 } 197 dc = chal.dc; 198 challenge = chal.challenge; 199 } else { 200 dc = UniAddress.getByName(domainController, true); 201 dc = UniAddress.getByName(dc.getHostAddress(), true); 202 challenge = SmbSession.getChallenge(dc); 203 } 204 205 ntlm = NtlmSsp.authenticate(req, resp, challenge); 206 if (ntlm == null) { 207 log.debug("NtlmSsp.authenticate returned null"); 208 return null; 209 } 210 211 log.debug("NtlmSsp.authenticate succeed"); 212 log.debug("Domain controller is " + dc.getHostName()); 213 if (ntlm.getDomain() != null) { 214 log.debug("NtlmSsp.authenticate => domain = " + ntlm.getDomain()); 215 } else { 216 log.debug("NtlmSsp.authenticate => null domain"); 217 } 218 if (ntlm.getUsername() != null) { 219 log.debug("NtlmSsp.authenticate => userName = " + ntlm.getUsername()); 220 } else { 221 log.debug("NtlmSsp.authenticate => userName = null"); 222 } 223 if (ntlm.getPassword() != null) { 224 log.debug("NtlmSsp.authenticate => password = " + ntlm.getPassword()); 225 } else { 226 log.debug("NtlmSsp.authenticate => password = null"); 227 } 228 229 /* negotiation complete, remove the challenge object */ 230 ssn.removeAttribute(NTLM_HTTP_CHAL_SESSION_KEY); 231 if (!skipAuthentication) { 232 try { 233 log.debug("Trying to logon NTLM session on dc " + dc.toString()); 234 SmbSession.logon(dc, ntlm); 235 log.debug(ntlm + " successfully authenticated against " + dc); 236 237 } catch (SmbAuthException sae) { 238 239 log.error(ntlm.getName() + ": 0x" + jcifs.util.Hexdump.toHexString(sae.getNtStatus(), 8) + ": " 240 + sae); 241 242 if (sae.getNtStatus() == NT_STATUS_ACCESS_VIOLATION) { 243 /* 244 * Server challenge no longer valid for externally supplied password hashes. 245 */ 246 ssn = req.getSession(false); 247 if (ssn != null) { 248 ssn.removeAttribute(NTLM_HTTP_AUTH_SESSION_KEY); 249 } 250 } 251 resp.setHeader("WWW-Authenticate", "NTLM"); 252 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 253 resp.setContentLength(0); 254 resp.flushBuffer(); 255 return null; 256 } 257 req.getSession().setAttribute(NTLM_HTTP_AUTH_SESSION_KEY, ntlm); 258 } 259 } else { 260 log.debug("NTLM negotiation header is null"); 261 return null; 262 } 263 return ntlm; 264 } 265 266}