1 17 package org.alfresco.web.app.servlet; 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.Locale ; 26 import java.util.Random ; 27 28 import javax.servlet.Filter ; 29 import javax.servlet.FilterChain ; 30 import javax.servlet.FilterConfig ; 31 import javax.servlet.ServletContext ; 32 import javax.servlet.ServletException ; 33 import javax.servlet.ServletRequest ; 34 import javax.servlet.ServletResponse ; 35 import javax.servlet.http.HttpServletRequest ; 36 import javax.servlet.http.HttpServletResponse ; 37 import javax.servlet.http.HttpSession ; 38 import javax.transaction.UserTransaction ; 39 40 import net.sf.acegisecurity.BadCredentialsException; 41 42 import org.alfresco.config.ConfigService; 43 import org.alfresco.filesys.server.auth.PasswordEncryptor; 44 import org.alfresco.filesys.server.auth.ntlm.NTLM; 45 import org.alfresco.filesys.server.auth.ntlm.NTLMLogonDetails; 46 import org.alfresco.filesys.server.auth.ntlm.NTLMMessage; 47 import org.alfresco.filesys.server.auth.ntlm.TargetInfo; 48 import org.alfresco.filesys.server.auth.ntlm.Type1NTLMMessage; 49 import org.alfresco.filesys.server.auth.ntlm.Type2NTLMMessage; 50 import org.alfresco.filesys.server.auth.ntlm.Type3NTLMMessage; 51 import org.alfresco.filesys.server.config.ServerConfiguration; 52 import org.alfresco.filesys.util.DataPacker; 53 import org.alfresco.i18n.I18NUtil; 54 import org.alfresco.model.ContentModel; 55 import org.alfresco.repo.security.authentication.AuthenticationComponent; 56 import org.alfresco.repo.security.authentication.AuthenticationException; 57 import org.alfresco.repo.security.authentication.MD4PasswordEncoder; 58 import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl; 59 import org.alfresco.repo.security.authentication.NTLMMode; 60 import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; 61 import org.alfresco.service.ServiceRegistry; 62 import org.alfresco.service.cmr.repository.NodeRef; 63 import org.alfresco.service.cmr.repository.NodeService; 64 import org.alfresco.service.cmr.security.AuthenticationService; 65 import org.alfresco.service.cmr.security.PersonService; 66 import org.alfresco.service.transaction.TransactionService; 67 import org.alfresco.web.app.Application; 68 import org.alfresco.web.bean.LoginBean; 69 import org.alfresco.web.bean.repository.User; 70 import org.alfresco.web.config.LanguagesConfigElement; 71 import org.apache.commons.codec.binary.Base64; 72 import org.apache.commons.logging.Log; 73 import org.apache.commons.logging.LogFactory; 74 import org.springframework.web.context.WebApplicationContext; 75 import org.springframework.web.context.support.WebApplicationContextUtils; 76 77 82 public class NTLMAuthenticationFilter extends AbstractAuthenticationFilter implements Filter 83 { 84 86 public static final String NTLM_AUTH_SESSION = "_alfNTLMAuthSess"; 87 public static final String NTLM_AUTH_DETAILS = "_alfNTLMDetails"; 88 89 91 private static final String LOCALE = "locale"; 92 public static final String MESSAGE_BUNDLE = "alfresco.messages.webclient"; 93 94 96 private static final int NTLM_FLAGS = NTLM.Flag56Bit + NTLM.FlagLanManKey + NTLM.FlagNegotiateNTLM + 97 NTLM.FlagNegotiateOEM + NTLM.FlagNegotiateUnicode; 98 99 101 private static Log logger = LogFactory.getLog(NTLMAuthenticationFilter.class); 102 103 105 private ServletContext m_context; 106 107 109 private ServerConfiguration m_srvConfig; 110 111 113 private AuthenticationService m_authService; 114 private AuthenticationComponent m_authComponent; 115 private PersonService m_personService; 116 private NodeService m_nodeService; 117 private TransactionService m_transactionService; 118 private ConfigService m_configService; 119 120 122 private PasswordEncryptor m_encryptor = new PasswordEncryptor(); 123 124 126 private boolean m_allowGuest; 127 128 130 private String m_loginPage; 131 132 134 private Random m_random = new Random (System.currentTimeMillis()); 135 136 138 private MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl(); 139 140 142 private String m_srvName; 143 144 146 private List <String > m_languages; 147 148 154 public void init(FilterConfig args) throws ServletException 155 { 156 158 m_context = args.getServletContext(); 159 160 162 WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(m_context); 163 164 ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); 165 m_nodeService = serviceRegistry.getNodeService(); 166 m_transactionService = serviceRegistry.getTransactionService(); 167 168 m_authService = (AuthenticationService) ctx.getBean("authenticationService"); 169 m_authComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); 170 m_personService = (PersonService) ctx.getBean("personService"); 171 m_configService = (ConfigService) ctx.getBean("webClientConfigService"); 172 173 m_srvConfig = (ServerConfiguration) ctx.getBean(ServerConfiguration.SERVER_CONFIGURATION); 174 175 177 if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && 178 m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) 179 { 180 throw new ServletException ("Required authentication mode not available"); 181 } 182 183 185 if ( m_srvConfig != null) 186 { 187 m_srvName = m_srvConfig.getServerName(); 188 189 if ( m_srvName == null) 190 { 191 194 m_srvName = m_srvConfig.getLocalServerName(true) + "_A"; 195 } 196 } 197 else 198 { 199 201 try 202 { 203 205 m_srvName = InetAddress.getLocalHost().getHostName(); 206 207 209 int pos = m_srvName.indexOf("."); 210 if ( pos != -1) 211 m_srvName = m_srvName.substring(0, pos - 1); 212 } 213 catch (UnknownHostException ex) 214 { 215 217 if ( logger.isErrorEnabled()) 218 logger.error("NTLM filter, error getting local host name", ex); 219 } 220 221 } 222 223 225 if ( m_srvName == null || m_srvName.length() == 0) 226 throw new ServletException ("Failed to get local server name"); 227 228 230 String guestAccess = args.getInitParameter("AllowGuest"); 231 if ( guestAccess != null) 232 { 233 m_allowGuest = Boolean.parseBoolean(guestAccess); 234 235 237 if ( logger.isDebugEnabled() && m_allowGuest) 238 logger.debug("NTLM filter guest access allowed"); 239 } 240 241 243 LanguagesConfigElement config = (LanguagesConfigElement) m_configService. 244 getConfig("Languages").getConfigElement(LanguagesConfigElement.CONFIG_ELEMENT_ID); 245 246 m_languages = config.getLanguages(); 247 } 248 249 258 public void doFilter(ServletRequest sreq, ServletResponse sresp, FilterChain chain) throws IOException , 259 ServletException 260 { 261 263 HttpServletRequest req = (HttpServletRequest ) sreq; 264 HttpServletResponse resp = (HttpServletResponse ) sresp; 265 266 HttpSession httpSess = req.getSession(true); 267 268 270 String authHdr = req.getHeader("Authorization"); 271 boolean reqAuth = false; 272 273 if ( authHdr != null && authHdr.startsWith("NTLM")) 274 reqAuth = true; 275 276 278 User user = (User) httpSess.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); 279 280 if ( user != null && reqAuth == false) 281 { 282 try 283 { 284 286 if ( logger.isDebugEnabled()) 287 logger.debug("User " + user.getUserName() + " validate ticket"); 288 289 291 m_authService.validate( user.getTicket()); 292 reqAuth = false; 293 294 296 I18NUtil.setLocale(Application.getLanguage(httpSess)); 297 } 298 catch (AuthenticationException ex) 299 { 300 if ( logger.isErrorEnabled()) 301 logger.error("Failed to validate user " + user.getUserName(), ex); 302 303 reqAuth = true; 304 } 305 } 306 307 310 if ( reqAuth == false && user != null) 311 { 312 314 if ( logger.isDebugEnabled()) 315 logger.debug("Authentication not required, chaining ..."); 316 317 319 chain.doFilter(sreq, sresp); 320 return; 321 } 322 323 325 if ( req.getRequestURI().endsWith(getLoginPage()) == true) 326 { 327 329 if ( logger.isDebugEnabled()) 330 logger.debug("Login page requested, chaining ..."); 331 332 334 chain.doFilter( sreq, sresp); 335 return; 336 } 337 338 341 String userAgent = req.getHeader("user-agent"); 342 343 if ( userAgent != null && userAgent.indexOf("Opera ") != -1) 344 { 345 347 if ( logger.isDebugEnabled()) 348 logger.debug("Opera detected, redirecting to login page"); 349 350 352 resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); 353 return; 354 } 355 356 358 if ( authHdr == null) { 359 360 362 if ( logger.isDebugEnabled()) 363 logger.debug("New NTLM auth request from " + req.getRemoteHost() + " (" + 364 req.getRemoteAddr() + ":" + req.getRemotePort() + ")"); 365 366 368 resp.setHeader("WWW-Authenticate", "NTLM"); 369 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 370 371 resp.flushBuffer(); 372 } 373 else { 374 375 377 NTLMLogonDetails ntlmDetails = null; 378 379 if ( httpSess != null) 380 { 381 ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS); 382 } 383 384 386 final byte[] ntlmByts = Base64.decodeBase64( authHdr.substring(5).getBytes()); 387 int ntlmTyp = NTLMMessage.isNTLMType(ntlmByts); 388 389 if ( ntlmTyp == NTLM.Type1) 390 { 391 393 Type1NTLMMessage type1Msg = new Type1NTLMMessage(ntlmByts); 394 processType1(type1Msg, req, resp, httpSess); 395 } 396 else if ( ntlmTyp == NTLM.Type3) 397 { 398 400 Type3NTLMMessage type3Msg = new Type3NTLMMessage(ntlmByts); 401 processType3(type3Msg, req, resp, httpSess, chain); 402 } 403 else 404 { 405 407 if ( logger.isDebugEnabled()) 408 logger.debug("NTLM not handled, redirecting to login page"); 409 410 412 resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); 413 } 414 } 415 } 416 417 422 private final boolean allowsGuest() 423 { 424 return m_allowGuest; 425 } 426 427 432 private String getLoginPage() 433 { 434 if (m_loginPage == null) 435 { 436 m_loginPage = Application.getLoginPage(m_context); 437 } 438 439 return m_loginPage; 440 } 441 442 445 public void destroy() 446 { 447 } 448 449 458 private void processType1(Type1NTLMMessage type1Msg, HttpServletRequest req, HttpServletResponse resp, 459 HttpSession httpSess) throws IOException 460 { 461 463 if ( logger.isDebugEnabled()) 464 logger.debug("Received type1 " + type1Msg); 465 466 468 NTLMLogonDetails ntlmDetails = null; 469 470 if ( httpSess != null) 471 { 472 ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS); 473 } 474 475 477 if ( ntlmDetails != null && ntlmDetails.hasType2Message() && ntlmDetails.hasNTLMHashedPassword() && 478 ntlmDetails.hasAuthenticationToken()) 479 { 480 482 Type2NTLMMessage cachedType2 = ntlmDetails.getType2Message(); 483 484 byte[] type2Bytes = cachedType2.getBytes(); 485 String ntlmBlob = "NTLM " + new String (Base64.encodeBase64(type2Bytes)); 486 487 489 if ( logger.isDebugEnabled()) 490 logger.debug("Sending cached NTLM type2 to client - " + cachedType2); 491 492 494 resp.setHeader("WWW-Authenticate", ntlmBlob); 495 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 496 497 resp.flushBuffer(); 498 return; 499 } 500 else 501 { 502 504 httpSess.removeAttribute(NTLM_AUTH_DETAILS); 505 506 508 byte[] challenge = null; 509 NTLMPassthruToken authToken = null; 510 511 if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) 512 { 513 515 challenge = new byte[8]; 516 DataPacker.putIntelLong(m_random.nextLong(), challenge, 0); 517 } 518 else 519 { 520 522 authToken = new NTLMPassthruToken(); 523 524 526 m_authComponent.authenticate( authToken); 527 528 530 if ( authToken.getChallenge() != null) 531 challenge = authToken.getChallenge().getBytes(); 532 } 533 534 536 int ntlmFlags = type1Msg.getFlags() & NTLM_FLAGS; 537 538 540 List <TargetInfo> tList = new ArrayList <TargetInfo>(); 541 tList.add(new TargetInfo(NTLM.TargetServer, m_srvName)); 542 543 Type2NTLMMessage type2Msg = new Type2NTLMMessage(); 544 type2Msg.buildType2(ntlmFlags, m_srvName, challenge, null, tList); 545 546 548 ntlmDetails = new NTLMLogonDetails(); 549 ntlmDetails.setType2Message( type2Msg); 550 ntlmDetails.setAuthenticationToken(authToken); 551 552 httpSess.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); 553 554 556 if ( logger.isDebugEnabled()) 557 logger.debug("Sending NTLM type2 to client - " + type2Msg); 558 559 561 byte[] type2Bytes = type2Msg.getBytes(); 562 String ntlmBlob = "NTLM " + new String (Base64.encodeBase64(type2Bytes)); 563 564 resp.setHeader("WWW-Authenticate", ntlmBlob); 565 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 566 567 resp.flushBuffer(); 568 return; 569 } 570 } 571 572 583 private void processType3(Type3NTLMMessage type3Msg, HttpServletRequest req, HttpServletResponse resp, 584 HttpSession httpSess, FilterChain chain) throws IOException , ServletException 585 { 586 588 if ( logger.isDebugEnabled()) 589 logger.debug("Received type3 " + type3Msg); 590 591 593 NTLMLogonDetails ntlmDetails = null; 594 User user = null; 595 596 if ( httpSess != null) 597 { 598 ntlmDetails = (NTLMLogonDetails) httpSess.getAttribute(NTLM_AUTH_DETAILS); 599 user = (User) httpSess.getAttribute(AuthenticationHelper.AUTHENTICATION_USER); 600 } 601 602 604 String userName = type3Msg.getUserName(); 605 String workstation = type3Msg.getWorkstation(); 606 String domain = type3Msg.getDomain(); 607 608 boolean authenticated = false; 609 boolean useNTLM = true; 610 611 613 if ( user != null && ntlmDetails != null && ntlmDetails.hasNTLMHashedPassword()) 614 { 615 617 byte[] ntlmPwd = type3Msg.getNTLMHash(); 618 byte[] cachedPwd = ntlmDetails.getNTLMHashedPassword(); 619 620 if ( ntlmPwd != null) 621 { 622 if ( ntlmPwd.length == cachedPwd.length) 623 { 624 authenticated = true; 625 for ( int i = 0; i < ntlmPwd.length; i++) 626 { 627 if ( ntlmPwd[i] != cachedPwd[i]) 628 authenticated = false; 629 } 630 } 631 } 632 633 635 if ( logger.isDebugEnabled()) 636 logger.debug("Using cached NTLM hash, authenticated = " + authenticated); 637 638 try 639 { 640 642 if ( logger.isDebugEnabled()) 643 logger.debug("User " + user.getUserName() + " validate ticket"); 644 645 647 m_authService.validate( user.getTicket()); 648 649 651 I18NUtil.setLocale(Application.getLanguage(httpSess)); 652 } 653 catch (AuthenticationException ex) 654 { 655 if ( logger.isErrorEnabled()) 656 logger.error("Failed to validate user " + user.getUserName(), ex); 657 658 660 resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); 661 return; 662 } 663 664 666 chain.doFilter( req, resp); 667 return; 668 } 669 else 670 { 671 673 if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) 674 { 675 677 String md4hash = m_authComponent.getMD4HashedPassword(userName); 678 679 if ( md4hash != null) 680 { 681 683 byte[] p21 = new byte[21]; 684 byte[] md4byts = m_md4Encoder.decodeHash(md4hash); 685 System.arraycopy(md4byts, 0, p21, 0, 16); 686 687 689 byte[] localHash = null; 690 691 try 692 { 693 localHash = m_encryptor.doNTLM1Encryption(p21, ntlmDetails.getChallengeKey()); 694 } 695 catch (NoSuchAlgorithmException ex) 696 { 697 } 698 699 701 byte[] clientHash = type3Msg.getNTLMHash(); 702 703 if ( clientHash != null && localHash != null && clientHash.length == localHash.length) 704 { 705 int i = 0; 706 707 while ( i < clientHash.length && clientHash[i] == localHash[i]) 708 i++; 709 710 if ( i == clientHash.length) 711 authenticated = true; 712 } 713 } 714 else 715 { 716 718 if ( logger.isDebugEnabled()) 719 logger.debug("User " + userName + " does not have Alfresco account"); 720 721 724 authenticated = false; 725 } 726 } 727 else 728 { 729 731 NTLMPassthruToken authToken = (NTLMPassthruToken) ntlmDetails.getAuthenticationToken(); 732 authToken.setUserAndPassword( type3Msg.getUserName(), type3Msg.getNTLMHash(), PasswordEncryptor.NTLM1); 733 734 try 735 { 736 738 m_authComponent.authenticate(authToken); 739 authenticated = true; 740 } 741 catch (BadCredentialsException ex) 742 { 743 745 if ( logger.isDebugEnabled()) 746 logger.debug("Authentication failed, " + ex.getMessage()); 747 } 748 catch (AuthenticationException ex) 749 { 750 752 if ( logger.isDebugEnabled()) 753 logger.debug("Authentication failed, " + ex.getMessage()); 754 } 755 finally 756 { 757 759 ntlmDetails.setAuthenticationToken(null); 760 } 761 } 762 763 765 if ( authenticated == true) 766 { 767 UserTransaction tx = m_transactionService.getUserTransaction(); 768 NodeRef homeSpaceRef = null; 769 770 try 771 { 772 tx.begin(); 773 774 m_authComponent.setCurrentUser(userName.toLowerCase()); 776 777 userName = m_authComponent.getCurrentUserName(); 780 781 NodeRef personNodeRef = m_personService.getPerson(userName); 783 String currentTicket = m_authService.getCurrentTicket(); 784 user = new User(userName, currentTicket, personNodeRef); 785 786 homeSpaceRef = (NodeRef) m_nodeService.getProperty( 787 personNodeRef, 788 ContentModel.PROP_HOMEFOLDER); 789 user.setHomeSpaceId(homeSpaceRef.getId()); 790 791 tx.commit(); 793 } 794 catch (Throwable ex) 795 { 796 try 797 { 798 tx.rollback(); 799 } 800 catch (Exception ex2) 801 { 802 logger.error("Failed to rollback transaction", ex2); 803 } 804 if(ex instanceof RuntimeException ) 805 { 806 throw (RuntimeException )ex; 807 } 808 else if(ex instanceof IOException ) 809 { 810 throw (IOException )ex; 811 } 812 else if(ex instanceof ServletException ) 813 { 814 throw (ServletException )ex; 815 } 816 else 817 { 818 throw new RuntimeException ("Authentication setup failed", ex); 819 } 820 } 821 822 824 httpSess.setAttribute(AuthenticationHelper.AUTHENTICATION_USER, user); 825 httpSess.setAttribute(LoginBean.LOGIN_EXTERNAL_AUTH, Boolean.TRUE); 826 827 829 Locale userLocale = parseAcceptLanguageHeader(req, m_languages); 830 831 if ( userLocale != null) 832 { 833 httpSess.setAttribute(LOCALE, userLocale); 834 httpSess.removeAttribute(MESSAGE_BUNDLE); 835 } 836 837 839 I18NUtil.setLocale(Application.getLanguage(httpSess)); 840 841 843 if ( ntlmDetails == null) 844 { 845 847 ntlmDetails = new NTLMLogonDetails( userName, workstation, domain, false, m_srvName); 848 849 httpSess.setAttribute(NTLM_AUTH_DETAILS, ntlmDetails); 850 851 853 if ( logger.isDebugEnabled()) 854 logger.debug("No cached NTLM details, created"); 855 856 } 857 else 858 { 859 861 ntlmDetails.setDetails(userName, workstation, domain, false, m_srvName); 862 ntlmDetails.setNTLMHashedPassword(type3Msg.getNTLMHash()); 863 864 866 if ( logger.isDebugEnabled()) 867 logger.debug("Updated cached NTLM details"); 868 } 869 870 872 if ( logger.isDebugEnabled()) 873 logger.debug("User logged on via NTLM, " + ntlmDetails); 874 875 877 if (req.getRequestURI().endsWith(getLoginPage()) == true) 878 { 879 881 if ( logger.isDebugEnabled()) 882 logger.debug("Login page requested, redirecting to browse page"); 883 884 886 resp.sendRedirect(req.getContextPath() + "/faces/jsp/browse/browse.jsp"); 887 return; 888 } 889 else 890 { 891 893 chain.doFilter( req, resp); 894 return; 895 } 896 } 897 else 898 { 899 902 if (useNTLM == true) 903 { 904 906 httpSess.removeAttribute(NTLM_AUTH_SESSION); 907 httpSess.removeAttribute(NTLM_AUTH_DETAILS); 908 909 911 resp.setHeader("WWW-Authenticate", "NTLM"); 912 resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 913 914 resp.flushBuffer(); 915 return; 916 } 917 else 918 { 919 921 if ( logger.isDebugEnabled()) 922 logger.debug("Redirecting to login page"); 923 924 926 resp.sendRedirect(req.getContextPath() + "/faces" + getLoginPage()); 927 return; 928 } 929 } 930 } 931 } 932 } 933 | Popular Tags |