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