View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.security.impl.ntlm;
18  
19  import java.security.Principal;
20  import java.util.HashSet;
21  import java.util.Set;
22  
23  import javax.security.auth.Subject;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.jetspeed.administration.PortalAuthenticationConfiguration;
27  import org.apache.jetspeed.pipeline.PipelineException;
28  import org.apache.jetspeed.request.RequestContext;
29  import org.apache.jetspeed.security.SecurityException;
30  import org.apache.jetspeed.security.SecurityHelper;
31  import org.apache.jetspeed.security.User;
32  import org.apache.jetspeed.security.UserManager;
33  import org.apache.jetspeed.security.UserPrincipal;
34  import org.apache.jetspeed.security.impl.AbstractSecurityValve;
35  import org.apache.jetspeed.security.impl.UserPrincipalImpl;
36  import org.apache.jetspeed.statistics.PortalStatistics;
37  /***
38   * NTLMSecurityValve provides Subject creation based on the
39   * NTLM provided request.getRemoteUser() user name. When request.getRemoteUser() holds
40   * a valid value, then this user is authorized. Otherwise the username is retrieved
41   * from the Principal name in the request. In this way you can use NTLM authentication, with
42   * a fallback authentication method in case the user is not properly authenticated / authorized using
43   * NTLM. 
44   * 
45   * There are basically three authentication scenarios:
46   * <ol>
47   *   <li>
48   *     <p><b>The user is successfully authenticated and authorized by Ntml authentication</b></p>
49   *     <p>A Subject is created, with Principal derived from the remoteUser value from Ntlm authentication</p>
50   *   </li>
51   *   <li> 
52   *     <p><b>The user is not authenticated by Ntlm, or the authenticated (can be NTLM or any other method) user cannot be authorized by Jetspeed.</b></p>
53   *     <p>An anonymous Subject is created. The user can then be redirected to a login page for example.</p>
54   *   </li>
55   *   <li> 
56   *     <p><b>The user is authenticated by a (non-NTLM) authentication method, e.g. container-based form authentication.</b></p>
57   *     <p>
58   *       A subject is created based on the Principal name in the request.
59   *     </p>
60   *   </li>
61   * </ol>
62   * @author <a href="mailto:taylor@apache.org">David Sean Taylor </a>
63   * @author <a href="mailto:rwatler@finali.com">Randy Walter </a>
64   * @author <a href="mailto:weaver@apache.org">Scott T. Weaver</a>
65   * @author <a href="mailto:d.dam@hippo.nl">Dennis Dam</a>
66   * @version $Id$
67   */
68  public class NtlmSecurityValve extends AbstractSecurityValve 
69  {
70      private UserManager userMgr;
71      private PortalStatistics statistics;
72      private String networkDomain;
73      private boolean ntlmAuthRequired;
74      private boolean omitDomain;
75      
76      
77      /***
78       * @param userMgr A UserManager
79       * @param statistics Portal Statistics
80       * @param networkDomain The network domain is used in combination with the <code>omitDomain</code> flag. 
81       * @param omitDomain If <code>true</code>, then the network domain is stripped from the remoteUser name.
82       * @param ntlmAuthRequired if <code>true</code>, then an exception is thrown when there is no valid remoteUser,
83       * or the remoteUser cannot be authorized.
84       * 
85       */
86      public NtlmSecurityValve(UserManager userMgr, String networkDomain, boolean omitDomain, boolean ntlmAuthRequired, 
87              PortalStatistics statistics, PortalAuthenticationConfiguration authenticationConfiguration) 
88      {
89          this.userMgr = userMgr;
90          this.statistics = statistics;
91          this.networkDomain = networkDomain;
92          this.ntlmAuthRequired = ntlmAuthRequired;
93          this.omitDomain = omitDomain;
94          this.authenticationConfiguration = authenticationConfiguration;
95      }
96  
97      public NtlmSecurityValve(UserManager userMgr, String networkDomain, boolean omitDomain, boolean ntlmAuthRequired, PortalStatistics statistics)
98      {
99          this(userMgr, networkDomain, omitDomain, ntlmAuthRequired, statistics, null);        
100     }
101     
102     public NtlmSecurityValve(UserManager userMgr, String networkDomain, boolean omitDomain, boolean ntlmAuthRequired)
103     {
104         this(userMgr, networkDomain, omitDomain, ntlmAuthRequired, null);
105     }
106 
107     public String toString()
108     {
109         return "NtlmSecurityValve";
110     }
111  
112     protected Principal getUserPrincipal(RequestContext context) throws Exception 
113     {
114         Subject subject = getSubjectFromSession(context);
115         if (subject != null)
116         {
117             return SecurityHelper.getPrincipal(subject, UserPrincipal.class);
118         } 
119         // otherwise return anonymous principal
120         return new UserPrincipalImpl(userMgr.getAnonymousUser());
121     }
122 
123     protected Subject getSubject(RequestContext context) throws Exception 
124     {
125         Subject subject = getSubjectFromSession(context);
126         // Get remote user name set by web container
127         String userName = context.getRequest().getRemoteUser();
128         if ( userName == null )
129         {            
130             if (ntlmAuthRequired){
131                 throw new PipelineException("Authorization failed.");    
132             } else if (context.getRequest().getUserPrincipal() != null){
133                 userName = context.getRequest().getUserPrincipal().getName();
134             }             
135         } else {
136             if (omitDomain && networkDomain != null){
137                 userName = StringUtils.stripStart( userName , networkDomain+"//");
138             }
139         }
140         
141         // check whether principal name stored in session subject equals the remote user name passed by the web container
142         if (subject != null)
143         {
144             Principal subjectUserPrincipal = SecurityHelper.getPrincipal(subject, UserPrincipal.class);
145             if ((subjectUserPrincipal == null) || !subjectUserPrincipal.getName().equals(userName))
146             {
147                 subject = null;
148             }
149         }
150         if ( subject == null ){
151             if (userName != null){
152                 try
153                 {                    
154                     User user = userMgr.getUser(userName);
155                     if ( user != null )
156                     {
157                         subject = user.getSubject();
158                     }
159                 } catch (SecurityException sex)
160                 {
161                     subject = null;
162                 }
163                 
164                 if (subject == null && this.ntlmAuthRequired){
165                     throw new PipelineException("Authorization failed for user '"+userName+"'.");
166                 }
167             }  
168             if (subject == null){
169                 // create anonymous user
170                 Principal userPrincipal = getUserPrincipal(context);
171                 Set principals = new HashSet();
172                 principals.add(userPrincipal);
173                 subject = new Subject(true, principals, new HashSet(), new HashSet()); 
174             }
175             
176             // create a new statistics *user* session
177             if (statistics != null)
178             {
179                 statistics.logUserLogin(context, 0);
180             }
181             // put IP address in session for logout
182             context.setSessionAttribute(IP_ADDRESS, context.getRequest().getRemoteAddr());            
183         }        
184         
185         return subject;
186     }
187 }