1 17 package org.alfresco.repo.webdav.auth; 18 19 import java.io.IOException ; 20 import java.net.InetAddress ; 21 import java.net.UnknownHostException ; 22 import java.security.NoSuchAlgorithmException ; 23 import java.util.ArrayList ; 24 import java.util.List ; 25 import java.util.Random ; 26 27 import javax.servlet.Filter ; 28 import javax.servlet.FilterChain ; 29 import javax.servlet.FilterConfig ; 30 import javax.servlet.ServletContext ; 31 import javax.servlet.ServletException ; 32 import javax.servlet.ServletRequest ; 33 import javax.servlet.ServletResponse ; 34 import javax.servlet.http.HttpServletRequest ; 35 import javax.servlet.http.HttpServletResponse ; 36 import javax.servlet.http.HttpSession ; 37 import javax.transaction.UserTransaction ; 38 39 import org.alfresco.filesys.server.auth.PasswordEncryptor; 40 import org.alfresco.filesys.server.auth.ntlm.NTLM; 41 import org.alfresco.filesys.server.auth.ntlm.NTLMLogonDetails; 42 import org.alfresco.filesys.server.auth.ntlm.NTLMMessage; 43 import org.alfresco.filesys.server.auth.ntlm.TargetInfo; 44 import org.alfresco.filesys.server.auth.ntlm.Type1NTLMMessage; 45 import org.alfresco.filesys.server.auth.ntlm.Type2NTLMMessage; 46 import org.alfresco.filesys.server.auth.ntlm.Type3NTLMMessage; 47 import org.alfresco.filesys.server.config.ServerConfiguration; 48 import org.alfresco.filesys.util.DataPacker; 49 import org.alfresco.model.ContentModel; 50 import org.alfresco.repo.security.authentication.AuthenticationComponent; 51 import org.alfresco.repo.security.authentication.AuthenticationException; 52 import org.alfresco.repo.security.authentication.MD4PasswordEncoder; 53 import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl; 54 import org.alfresco.repo.security.authentication.NTLMMode; 55 import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; 56 import org.alfresco.service.ServiceRegistry; 57 import org.alfresco.service.cmr.repository.NodeRef; 58 import org.alfresco.service.cmr.repository.NodeService; 59 import org.alfresco.service.cmr.security.AuthenticationService; 60 import org.alfresco.service.cmr.security.PersonService; 61 import org.alfresco.service.transaction.TransactionService; 62 import org.apache.commons.codec.binary.Base64; 63 import org.apache.commons.logging.Log; 64 import org.apache.commons.logging.LogFactory; 65 import org.springframework.web.context.WebApplicationContext; 66 import org.springframework.web.context.support.WebApplicationContextUtils; 67 68 73 public class NTLMAuthenticationFilter implements Filter 74 { 75 77 public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess"; 78 public static final String NTLM_AUTH_DETAILS = "_alfNTLMDetails"; 79 80 82 public final static String AUTHENTICATION_USER = "_alfDAVAuthTicket"; 83 84 86 private static final int NTLM_FLAGS = NTLM.Flag56Bit + NTLM.FlagLanManKey + NTLM.FlagNegotiateNTLM + 87 NTLM.FlagNegotiateOEM + NTLM.FlagNegotiateUnicode; 88 89 91 private static Log logger = LogFactory.getLog(NTLMAuthenticationFilter.class); 92 93 95 private ServletContext m_context; 96 97 99 private ServerConfiguration m_srvConfig; 100 101 103 private AuthenticationService m_authService; 104 private AuthenticationComponent m_authComponent; 105 private PersonService m_personService; 106 private NodeService m_nodeService; 107 private TransactionService m_transactionService; 108 109 111 private PasswordEncryptor m_encryptor = new PasswordEncryptor(); 112 113 115 private boolean m_allowGuest; 116 117 119 private String m_loginPage; 120 121 123 private Random m_random = new Random (System.currentTimeMillis()); 124 125 127 private MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl(); 128 129 131 private String m_srvName; 132 133 139 public void init(FilterConfig args) throws ServletException 140 { 141 143 m_context = args.getServletContext(); 144 145 147 WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); 148 149 ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); 150 m_nodeService = serviceRegistry.getNodeService(); 151 m_transactionService = serviceRegistry.getTransactionService(); 152 153 m_authService = (AuthenticationService) ctx.getBean("authenticationService"); 154 m_authComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); 155 m_personService = (PersonService) ctx.getBean("personService"); 156 157 m_srvConfig = (ServerConfiguration) ctx.getBean(ServerConfiguration.SERVER_CONFIGURATION); 158 159 161 if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && 162 m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) 163 { 164 throw new ServletException ("Required authentication mode not available"); 165 } 166 167 169 if ( m_srvConfig != null) 170 { 171 m_srvName = m_srvConfig.getLocalServerName( true); 172 } 173 else 174 { 175 177 try 178 { 179 181 m_srvName = InetAddress.getLocalHost().getHostName(); 182 183 185 int pos = m_srvName.indexOf("."); 186 if ( pos != -1) 187 m_srvName = m_srvName.substring(0, pos - 1); 188 } 189 catch (UnknownHostException ex) 190 { 191 193 if ( logger.isErrorEnabled()) 194 logger.error("NTLM filter, error getting local host name", ex); 195 } 196 197 } 198 199 201 if ( m_srvName == null || m_srvName.length() == 0) 202 throw new ServletException ("Failed to get local server name"); 203 204 206 String guestAccess = args.getInitParameter("AllowGuest"); 207 if ( guestAccess != null) 208 { 209 m_allowGuest = Boolean.parseBoolean(guestAccess); 210 211 213 if ( logger.isDebugEnabled() && m_allowGuest) 214 logger.debug("NTLM filter guest access allowed"); 215 } 216 } 217 218 227 public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException , 228 ServletException 229 { 230 232 HttpServletRequest req = (HttpServletRequest ) sreq; 233 HttpServletResponse resp = (HttpServletResponse ) sresp; 234 235 HttpSession httpSess = req.getSession(true); 236 237 239 String authHdr = req.getHeader("Authorization"); 240 boolean reqAuth = false; 241 242 if ( authHdr != null && authHdr.startsWith("NTLM")) 243 reqAuth = true; 244 245 247 WebDAVUser user = (WebDAVUser) httpSess.getAttribute(AUTHENTICATION_USER); 248 249 if ( user != null && reqAuth == false) 250 { 251 try 252 { 253 255 if ( logger.isDebugEnabled()) 256 logger.debug("User " + user.getUserName() + " validate ticket"); 257 258 260 m_authService.validate( user.getTicket()); 261 reqAuth = false; 262 } 263 catch (AuthenticationException ex) 264 { 265 if ( logger.isErrorEnabled()) 266 logger.error("Failed to validate user " + user.getUserName(), ex); 267 268 reqAuth = true; 269 } 270 } 271 272 275 if ( reqAuth == false && user != null) 276 { 277 279 if ( logger.isDebugEnabled()) 280 logger.debug("Authentication not required, chaining ..."); 281 282 284 chain.doFilter(sreq, sresp); 285 return; 286 } 287 288 290 if ( authHdr == null) { 291 292 294 if ( logger.isDebugEnabled()) 295 logger.debug("New NTLM auth request from " + req.getRemoteHost() + " (" + 296 req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); 297 298 300 resp.setHeader("WWW-Authenticate", "NTLM"); 301 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 302 303 resp.flushBuffer(); 304 return; 305 } 306 else { 307 308 310 NTLMLogonDetails ntlmDetails = null; 311 312 if ( httpSess != null) 313 { 314 ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS); 315 } 316 317 319 byte[] ntlmByts = Base64.decodeBase64( authHdr.substring(5).getBytes()); 320 int ntlmTyp = NTLMMessage.isNTLMType(ntlmByts); 321 322 if ( ntlmTyp == NTLM.Type1) 323 { 324 326 Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts); 327 processType1(type1Msg, req, resp, httpSess); 328 return; 329 } 330 else if ( ntlmTyp == NTLM.Type3) 331 { 332 334 Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts); 335 processType3(type3Msg, req, resp, httpSess, chain); 336 } 337 } 338 339 341 httpSess.removeAttribute(NTLM_AUTH_SESSION); 342 httpSess.removeAttribute(NTLM_AUTH_DETAILS); 343 344 346 resp.setHeader("WWW-Authenticate", "NTLM"); 347 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 348 349 resp.flushBuffer(); 350 } 351 352 357 private final boolean allowsGuest() 358 { 359 return m_allowGuest; 360 } 361 362 365 public void destroy() 366 { 367 } 368 369 378 private void processType1(Type1NTLMMessage type1Msg, HttpServletRequest req, HttpServletResponse resp, 379 HttpSession httpSess) throws IOException 380 { 381 383 if ( logger.isDebugEnabled()) 384 logger.debug("Received type1 " + type1Msg); 385 386 388 NTLMLogonDetails ntlmDetails = null; 389 390 if ( httpSess != null) 391 { 392 ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS); 393 } 394 395 397 if ( ntlmDetails != null && ntlmDetails.hasType2Message() && ntlmDetails.hasNTLMHashedPassword() && 398 ntlmDetails.hasAuthenticationToken()) 399 { 400 402 Type2NTLMMessage cachedType2 = ntlmDetails.getType2Message(); 403 404 byte[] type2Bytes = cachedType2.getBytes(); 405 String ntlmBlob = "NTLM " + new String (Base64.encodeBase64(type2Bytes)); 406 407 409 if ( logger.isDebugEnabled()) 410 logger.debug("Sending cached NTLM type2 to client - " + cachedType2); 411 412 414 resp.setHeader("WWW-Authenticate", ntlmBlob); 415 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 416 417 resp.flushBuffer(); 418 return; 419 } 420 else 421 { 422 424 httpSess.removeAttribute(NTLM_AUTH_DETAILS); 425 426 428 byte[] challenge = null; 429 NTLMPassthruToken authToken = null; 430 431 if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) 432 { 433 435 challenge = new byte[8]; 436 DataPacker.putIntelLong(m_random.nextLong(), challenge, 0); 437 } 438 else 439 { 440 442 authToken = new NTLMPassthruToken(); 443 444 446 m_authComponent.authenticate( authToken); 447 448 450 if ( authToken.getChallenge() != null) 451 challenge = authToken.getChallenge().getBytes(); 452 } 453 454 456 int ntlmFlags = type1Msg.getFlags() & NTLM_FLAGS; 457 458 460 List <TargetInfo> tList = new ArrayList <TargetInfo>(); 461 tList.add(new TargetInfo(NTLM.TargetServer, m_srvName)); 462 463 Type2NTLMMessage type2Msg = new Type2NTLMMessage(); 464 type2Msg.buildType2(ntlmFlags, m_srvName, challenge, null, tList); 465 466 468 ntlmDetails = new NTLMLogonDetails(); 469 ntlmDetails.setType2Message( type2Msg); 470 ntlmDetails.setAuthenticationToken(authToken); 471 472 httpSess.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); 473 474 476 if ( logger.isDebugEnabled()) 477 logger.debug("Sending NTLM type2 to client - " + type2Msg); 478 479 481 byte[] type2Bytes = type2Msg.getBytes(); 482 String ntlmBlob = "NTLM " + new String (Base64.encodeBase64(type2Bytes)); 483 484 resp.setHeader("WWW-Authenticate", ntlmBlob); 485 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 486 487 resp.flushBuffer(); 488 return; 489 } 490 } 491 492 503 private void processType3(Type3NTLMMessage type3Msg, HttpServletRequest req, HttpServletResponse resp, 504 HttpSession httpSess, FilterChain chain) throws IOException , ServletException 505 { 506 508 if ( logger.isDebugEnabled()) 509 logger.debug("Received type3 " + type3Msg); 510 511 513 NTLMLogonDetails ntlmDetails = null; 514 WebDAVUser user = null; 515 516 if ( httpSess != null) 517 { 518 ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS); 519 user = (WebDAVUser) httpSess.getAttribute(AUTHENTICATION_USER); 520 } 521 522 524 String userName = type3Msg.getUserName(); 525 String workstation = type3Msg.getWorkstation(); 526 String domain = type3Msg.getDomain(); 527 528 boolean authenticated = false; 529 boolean useNTLM = true; 530 531 533 if ( user != null && ntlmDetails != null && ntlmDetails.hasNTLMHashedPassword()) 534 { 535 537 byte[] ntlmPwd = type3Msg.getNTLMHash(); 538 byte[] cachedPwd = ntlmDetails.getNTLMHashedPassword(); 539 540 if ( ntlmPwd != null) 541 { 542 if ( ntlmPwd.length == cachedPwd.length) 543 { 544 authenticated = true; 545 for ( int i = 0; i < ntlmPwd.length; i++) 546 { 547 if ( ntlmPwd[i] != cachedPwd[i]) 548 authenticated = false; 549 } 550 } 551 } 552 553 555 if ( logger.isDebugEnabled()) 556 logger.debug("Using cached NTLM hash, authenticated = " + authenticated); 557 558 try 559 { 560 562 if ( logger.isDebugEnabled()) 563 logger.debug("User " + user.getUserName() + " validate ticket"); 564 565 567 m_authService.validate( user.getTicket()); 568 } 569 catch (AuthenticationException ex) 570 { 571 if ( logger.isErrorEnabled()) 572 logger.error("Failed to validate user " + user.getUserName(), ex); 573 574 576 httpSess.removeAttribute(NTLM_AUTH_SESSION); 577 httpSess.removeAttribute(NTLM_AUTH_DETAILS); 578 579 581 resp.setHeader("WWW-Authenticate", "NTLM"); 582 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 583 584 resp.flushBuffer(); 585 return; 586 } 587 588 590 chain.doFilter( req, resp); 591 return; 592 } 593 else 594 { 595 597 if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) 598 { 599 601 String md4hash = m_authComponent.getMD4HashedPassword(userName); 602 603 if ( md4hash != null) 604 { 605 607 byte[] p21 = new byte[21]; 608 byte[] md4byts = m_md4Encoder.decodeHash(md4hash); 609 System.arraycopy(md4byts, 0, p21, 0, 16); 610 611 613 byte[] localHash = null; 614 615 try 616 { 617 localHash = m_encryptor.doNTLM1Encryption(p21, ntlmDetails.getChallengeKey()); 618 } 619 catch (NoSuchAlgorithmException ex) 620 { 621 } 622 623 625 byte[] clientHash = type3Msg.getNTLMHash(); 626 627 if ( clientHash != null && localHash != null && clientHash.length == localHash.length) 628 { 629 int i = 0; 630 631 while ( i < clientHash.length && clientHash[i] == localHash[i]) 632 i++; 633 634 if ( i == clientHash.length) 635 authenticated = true; 636 } 637 } 638 else 639 { 640 642 if ( logger.isDebugEnabled()) 643 logger.debug("User " + userName + " does not have Alfresco account"); 644 645 648 authenticated = false; 649 } 650 } 651 else 652 { 653 655 NTLMPassthruToken authToken = (NTLMPassthruToken) ntlmDetails.getAuthenticationToken(); 656 authToken.setUserAndPassword( type3Msg.getUserName(), type3Msg.getNTLMHash(), PasswordEncryptor.NTLM1); 657 658 try 659 { 660 662 m_authComponent.authenticate(authToken); 663 authenticated = true; 664 } 665 catch (AuthenticationException ex) 666 { 667 669 if ( logger.isDebugEnabled()) 670 logger.debug("Authentication failed, " + ex.getMessage()); 671 } 672 finally 673 { 674 676 ntlmDetails.setAuthenticationToken(null); 677 } 678 } 679 680 682 if ( authenticated == true) 683 { 684 UserTransaction tx = m_transactionService.getUserTransaction(); 685 686 try 687 { 688 tx.begin(); 689 690 m_authComponent.setCurrentUser(userName.toLowerCase()); 692 693 userName = m_authComponent.getCurrentUserName(); 696 697 NodeRef personNodeRef = m_personService.getPerson(userName); 699 String currentTicket = m_authService.getCurrentTicket(); 700 user = new WebDAVUser(userName, currentTicket, personNodeRef); 701 702 NodeRef homeSpaceRef = (NodeRef) m_nodeService.getProperty(personNodeRef, ContentModel.PROP_HOMEFOLDER); 703 user.setHomeNode(homeSpaceRef); 704 705 tx.commit(); 707 } 708 catch (Throwable ex) 709 { 710 try 711 { 712 tx.rollback(); 713 } 714 catch (Exception ex2) 715 { 716 logger.error("Failed to rollback transaction", ex2); 717 } 718 if(ex instanceof RuntimeException ) 719 { 720 throw (RuntimeException )ex; 721 } 722 else if(ex instanceof IOException ) 723 { 724 throw (IOException )ex; 725 } 726 else if(ex instanceof ServletException ) 727 { 728 throw (ServletException )ex; 729 } 730 else 731 { 732 throw new RuntimeException ("Authentication setup failed", ex); 733 } 734 } 735 736 738 httpSess.setAttribute(AUTHENTICATION_USER, user); 739 740 742 if ( ntlmDetails == null) 743 { 744 746 ntlmDetails = new NTLMLogonDetails( userName, workstation, domain, false, m_srvName); 747 748 httpSess.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); 749 750 752 if ( logger.isDebugEnabled()) 753 logger.debug("No cached NTLM details, created"); 754 755 } 756 else 757 { 758 760 ntlmDetails.setDetails(userName, workstation, domain, false, m_srvName); 761 ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash()); 762 763 765 if ( logger.isDebugEnabled()) 766 logger.debug("Updated cached NTLM details"); 767 } 768 769 771 if ( logger.isDebugEnabled()) 772 logger.debug("User logged on via NTLM, " + ntlmDetails); 773 774 776 chain.doFilter( req, resp); 777 return; 778 } 779 else 780 { 781 783 httpSess.removeAttribute(NTLM_AUTH_SESSION); 784 httpSess.removeAttribute(NTLM_AUTH_DETAILS); 785 786 788 resp.setHeader("WWW-Authenticate", "NTLM"); 789 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 790 791 resp.flushBuffer(); 792 return; 793 } 794 } 795 } 796 } 797 | Popular Tags |