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