View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  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.geronimo.tomcat.realm;
18  
19  import org.apache.catalina.Context;
20  import org.apache.catalina.LifecycleException;
21  import org.apache.catalina.Wrapper;
22  import org.apache.catalina.connector.Request;
23  import org.apache.catalina.connector.Response;
24  import org.apache.catalina.deploy.LoginConfig;
25  import org.apache.catalina.deploy.SecurityConstraint;
26  import org.apache.catalina.realm.JAASRealm;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.geronimo.security.ContextManager;
30  import org.apache.geronimo.security.jacc.PolicyContextHandlerContainerSubject;
31  import org.apache.geronimo.security.realm.providers.CertificateChainCallbackHandler;
32  import org.apache.geronimo.security.realm.providers.PasswordCallbackHandler;
33  import org.apache.geronimo.tomcat.JAASTomcatPrincipal;
34  
35  import javax.security.auth.Subject;
36  import javax.security.auth.callback.CallbackHandler;
37  import javax.security.auth.login.AccountExpiredException;
38  import javax.security.auth.login.CredentialExpiredException;
39  import javax.security.auth.login.FailedLoginException;
40  import javax.security.auth.login.LoginContext;
41  import javax.security.auth.login.LoginException;
42  import javax.security.jacc.PolicyContext;
43  import javax.security.jacc.PolicyContextException;
44  import javax.security.jacc.WebResourcePermission;
45  import javax.security.jacc.WebRoleRefPermission;
46  import javax.security.jacc.WebUserDataPermission;
47  import javax.servlet.ServletRequest;
48  
49  import java.io.IOException;
50  import java.security.AccessControlContext;
51  import java.security.AccessControlException;
52  import java.security.Principal;
53  import java.security.cert.X509Certificate;
54  
55  
56  public class TomcatGeronimoRealm extends JAASRealm {
57  
58      private static final Log log = LogFactory.getLog(TomcatGeronimoRealm.class);
59  
60      private static ThreadLocal currentRequestWrapperName = new ThreadLocal();
61  
62      /**
63       * Descriptive information about this <code>Realm</code> implementation.
64       */
65      protected static final String info = "org.apache.geronimo.tomcat.TomcatGeronimoRealm/1.0";
66  
67      /**
68       * Descriptive information about this <code>Realm</code> implementation.
69       */
70      protected static final String name = "TomcatGeronimoRealm";
71  
72      public TomcatGeronimoRealm() {
73  
74       }
75  
76      public static String setRequestWrapperName(String requestWrapperName) {
77          String old = (String) currentRequestWrapperName.get();
78          currentRequestWrapperName.set(requestWrapperName);
79          return old;
80      }
81  
82      /**
83       * Enforce any user data constraint required by the security constraint
84       * guarding this request URI.  Return <code>true</code> if this constraint
85       * was not violated and processing should continue, or <code>false</code>
86       * if we have created a response already.
87       *
88       * @param request     Request we are processing
89       * @param response    Response we are creating
90       * @param constraints Security constraint being checked
91       * @throws IOException if an input/output error occurs
92       */
93      public boolean hasUserDataPermission(Request request,
94                                           Response response,
95                                           SecurityConstraint[] constraints)
96              throws IOException {
97  
98          //Get an authenticated subject, if there is one
99          Subject subject = null;
100         try {
101 
102             //We will use the PolicyContextHandlerContainerSubject.HANDLER_KEY to see if a user
103             //has authenticated, since a request.getUserPrincipal() will not pick up the user
104             //unless its using a cached session.
105             subject = (Subject) PolicyContext.getContext(PolicyContextHandlerContainerSubject.HANDLER_KEY);
106 
107         } catch (PolicyContextException e) {
108             log.error(e);
109         }
110 
111         //If nothing has authenticated yet, do the normal
112         if (subject == null)
113             return super.hasUserDataPermission(request, response, constraints);
114 
115         ContextManager.setCallers(subject, subject);
116 
117         try {
118 
119             AccessControlContext acc = ContextManager.getCurrentContext();
120 
121             /**
122              * JACC v1.0 secion 4.1.1
123              */
124             WebUserDataPermission wudp = new WebUserDataPermission(request);
125             acc.checkPermission(wudp);
126 
127         } catch (AccessControlException ace) {
128             response.sendError(Response.SC_FORBIDDEN);
129             return false;
130         }
131 
132         return true;
133     }
134 
135     /**
136      * Perform access control based on the specified authorization constraint.
137      * Return <code>true</code> if this constraint is satisfied and processing
138      * should continue, or <code>false</code> otherwise.
139      *
140      * @param request    Request we are processing
141      * @param response   Response we are creating
142      * @param constraints Security constraints we are enforcing
143      * @param context    The Context to which client of this class is attached.
144      * @throws java.io.IOException if an input/output error occurs
145      */
146     public boolean hasResourcePermission(Request request,
147                                          Response response,
148                                          SecurityConstraint[] constraints,
149                                          Context context)
150             throws IOException {
151 
152         // Specifically allow access to the form login and form error pages
153         // and the "j_security_check" action
154         LoginConfig config = context.getLoginConfig();
155         if ((config != null) &&
156             (org.apache.catalina.realm.Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
157             String requestURI = request.getDecodedRequestURI();
158             String loginPage = context.getPath() + config.getLoginPage();
159             if (loginPage.equals(requestURI)) {
160                 if (log.isDebugEnabled())
161                     log.debug(" Allow access to login page " + loginPage);
162                 return (true);
163             }
164             String errorPage = context.getPath() + config.getErrorPage();
165             if (errorPage.equals(requestURI)) {
166                 if (log.isDebugEnabled())
167                     log.debug(" Allow access to error page " + errorPage);
168                 return (true);
169             }
170             if (requestURI.endsWith(org.apache.catalina.realm.Constants.FORM_ACTION)) {
171                 if (log.isDebugEnabled())
172                     log.debug(" Allow access to username/password submission");
173                 return (true);
174             }
175         }
176         
177         //Set the current wrapper name (Servlet mapping)
178         currentRequestWrapperName.set(request.getWrapper().getName());
179 
180         // Which user principal have we already authenticated?
181         Principal principal = request.getUserPrincipal();
182 
183         //If we have no principal, then we should use the default.
184         if (principal == null) {
185             return request.isSecure();
186 
187         } else {
188             Subject currentCaller = ((JAASTomcatPrincipal) principal).getSubject();
189             ContextManager.setCallers(currentCaller, currentCaller);
190         }
191 
192         try {
193 
194             AccessControlContext acc = ContextManager.getCurrentContext();
195 
196 
197             /**
198              * JACC v1.0 section 4.1.2
199              */
200             acc.checkPermission(new WebResourcePermission(request));
201 
202         } catch (AccessControlException ace) {
203             response.sendError(Response.SC_FORBIDDEN);
204             return false;
205         }
206 
207         return true;
208 
209     }
210 
211     /**
212      * Return <code>true</code> if the specified Principal has the specified
213      * security role, within the context of this Realm; otherwise return
214      * <code>false</code>.
215      *
216      * @param principal Principal for whom the role is to be checked
217      * @param role      Security role to be checked
218      */
219     public boolean hasRole(Principal principal, String role) {
220 
221         if ((principal == null) || (role == null) || !(principal instanceof JAASTomcatPrincipal)) {
222             return false;
223         }
224 
225         String name = (String)currentRequestWrapperName.get();
226 
227         /**
228          * JACC v1.0 secion B.19
229          */
230         if (name == null || name.equals("jsp")) {
231             name = "";
232         }
233 
234         //Set the caller
235         Subject currentCaller = ((JAASTomcatPrincipal) principal).getSubject();
236         ContextManager.setCallers(currentCaller, currentCaller);
237 
238         AccessControlContext acc = ContextManager.getCurrentContext();
239 
240         try {
241             /**
242              * JACC v1.0 section 4.1.3
243              */
244             acc.checkPermission(new WebRoleRefPermission(name, role));
245         } catch (AccessControlException e) {
246             return false;
247         }
248 
249         return true;
250     }
251 
252     /**
253      * Return the <code>Principal</code> associated with the specified
254      * username and credentials, if there is one; otherwise return
255      * <code>null</code>.
256      * <p/>
257      * If there are any errors with the JDBC connection, executing the query or
258      * anything we return null (don't authenticate). This event is also logged,
259      * and the connection will be closed so that a subsequent request will
260      * automatically re-open it.
261      *
262      * @param username    Username of the <code>Principal</code> to look up
263      * @param credentials Password or other credentials to use in authenticating this
264      *                    username
265      */
266     public Principal authenticate(String username, String credentials) {
267 
268         char[] cred = credentials == null? null: credentials.toCharArray();
269         CallbackHandler callbackHandler = new PasswordCallbackHandler(username, cred);
270         return authenticate(callbackHandler, username);
271     }
272 
273     public Principal authenticate(X509Certificate[] certs) {
274         if (certs == null || certs.length == 0) {
275             return null;
276         }
277         CallbackHandler callbackHandler = new CertificateChainCallbackHandler(certs);
278         String principalName = certs[0].getSubjectX500Principal().getName();
279         return authenticate(callbackHandler, principalName);
280     }
281 
282     public Principal authenticate(CallbackHandler callbackHandler, String principalName) {
283 
284         // Establish a LoginContext to use for authentication
285         try {
286 
287             if ( (principalName!=null) && (!principalName.equals("")) ) {
288               LoginContext loginContext = null;
289               if (appName == null)
290                   appName = "Tomcat";
291 
292               if (log.isDebugEnabled())
293                   log.debug(sm.getString("jaasRealm.beginLogin", principalName, appName));
294 
295               // What if the LoginModule is in the container class loader ?
296               ClassLoader ocl = null;
297 
298               if (isUseContextClassLoader()) {
299                   ocl = Thread.currentThread().getContextClassLoader();
300                   Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
301               }
302 
303               try {
304                   loginContext = new LoginContext(appName, callbackHandler);
305               } catch (Throwable e) {
306                   log.error(sm.getString("jaasRealm.unexpectedError"), e);
307                   return (null);
308               } finally {
309                   if (isUseContextClassLoader()) {
310                       Thread.currentThread().setContextClassLoader(ocl);
311                   }
312               }
313 
314               if (log.isDebugEnabled())
315                   log.debug("Login context created " + principalName);
316 
317               // Negotiate a login via this LoginContext
318               Subject subject;
319               try {
320                   loginContext.login();
321                   Subject tempSubject = loginContext.getSubject();
322                   if (tempSubject == null) {
323                       if (log.isDebugEnabled())
324                           log.debug(sm.getString("jaasRealm.failedLogin", principalName));
325                       return (null);
326                   }
327 
328                   subject = ContextManager.getServerSideSubject(tempSubject);
329                   if (subject == null) {
330                       if (log.isDebugEnabled())
331                           log.debug(sm.getString("jaasRealm.failedLogin", principalName));
332                       return (null);
333                   }
334 
335                   ContextManager.setCallers(subject, subject);
336 
337               } catch (AccountExpiredException e) {
338                   if (log.isDebugEnabled())
339                       log.debug(sm.getString("jaasRealm.accountExpired", principalName));
340                   return (null);
341               } catch (CredentialExpiredException e) {
342                   if (log.isDebugEnabled())
343                       log.debug(sm.getString("jaasRealm.credentialExpired", principalName));
344                   return (null);
345               } catch (FailedLoginException e) {
346                   if (log.isDebugEnabled())
347                       log.debug(sm.getString("jaasRealm.failedLogin", principalName));
348                   return (null);
349               } catch (LoginException e) {
350                   log.warn(sm.getString("jaasRealm.loginException", principalName), e);
351                   return (null);
352               } catch (Throwable e) {
353                   log.error(sm.getString("jaasRealm.unexpectedError"), e);
354                   return (null);
355               }
356 
357               if (log.isDebugEnabled())
358                   log.debug(sm.getString("jaasRealm.loginContextCreated", principalName));
359 
360               // Return the appropriate Principal for this authenticated Subject
361   /*            Principal principal = createPrincipal(username, subject);
362               if (principal == null) {
363                   log.debug(sm.getString("jaasRealm.authenticateFailure", username));
364                   return (null);
365               }
366               if (log.isDebugEnabled()) {
367                   log.debug(sm.getString("jaasRealm.authenticateSuccess", username));
368               }
369   */
370               JAASTomcatPrincipal jaasPrincipal = new JAASTomcatPrincipal(principalName);
371               jaasPrincipal.setSubject(subject);
372 
373               return (jaasPrincipal);
374             }
375             else {
376                 if (log.isDebugEnabled())
377                     log.debug("Login Failed - null userID");
378                 return null;
379             }
380 
381         } catch (Throwable t) {
382             log.error("error ", t);
383             return null;
384         }
385     }
386     /**
387      * Prepare for active use of the public methods of this <code>Component</code>.
388      *
389      * @throws org.apache.catalina.LifecycleException
390      *          if this component detects a fatal error
391      *          that prevents it from being started
392      */
393     public void start() throws LifecycleException {
394 
395         // Perform normal superclass initialization
396         super.start();
397 
398     }
399 
400     /**
401      * Gracefully shut down active use of the public methods of this <code>Component</code>.
402      *
403      * @throws LifecycleException if this component detects a fatal error
404      *                            that needs to be reported
405      */
406     public void stop() throws LifecycleException {
407 
408         // Perform normal superclass finalization
409         super.stop();
410 
411     }
412 }