001 /** 002 * 003 * Copyright 2003-2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.tomcat.realm; 018 019 import org.apache.catalina.Context; 020 import org.apache.catalina.LifecycleException; 021 import org.apache.catalina.Wrapper; 022 import org.apache.catalina.connector.Request; 023 import org.apache.catalina.connector.Response; 024 import org.apache.catalina.deploy.LoginConfig; 025 import org.apache.catalina.deploy.SecurityConstraint; 026 import org.apache.catalina.realm.JAASRealm; 027 import org.apache.commons.logging.Log; 028 import org.apache.commons.logging.LogFactory; 029 import org.apache.geronimo.security.ContextManager; 030 import org.apache.geronimo.security.jacc.PolicyContextHandlerContainerSubject; 031 import org.apache.geronimo.security.realm.providers.CertificateChainCallbackHandler; 032 import org.apache.geronimo.security.realm.providers.PasswordCallbackHandler; 033 import org.apache.geronimo.tomcat.JAASTomcatPrincipal; 034 035 import javax.security.auth.Subject; 036 import javax.security.auth.callback.CallbackHandler; 037 import javax.security.auth.login.AccountExpiredException; 038 import javax.security.auth.login.CredentialExpiredException; 039 import javax.security.auth.login.FailedLoginException; 040 import javax.security.auth.login.LoginContext; 041 import javax.security.auth.login.LoginException; 042 import javax.security.jacc.PolicyContext; 043 import javax.security.jacc.PolicyContextException; 044 import javax.security.jacc.WebResourcePermission; 045 import javax.security.jacc.WebRoleRefPermission; 046 import javax.security.jacc.WebUserDataPermission; 047 import javax.servlet.ServletRequest; 048 049 import java.io.IOException; 050 import java.security.AccessControlContext; 051 import java.security.AccessControlException; 052 import java.security.Principal; 053 import java.security.cert.X509Certificate; 054 055 056 public class TomcatGeronimoRealm extends JAASRealm { 057 058 private static final Log log = LogFactory.getLog(TomcatGeronimoRealm.class); 059 060 private static ThreadLocal currentRequestWrapperName = new ThreadLocal(); 061 062 /** 063 * Descriptive information about this <code>Realm</code> implementation. 064 */ 065 protected static final String info = "org.apache.geronimo.tomcat.TomcatGeronimoRealm/1.0"; 066 067 /** 068 * Descriptive information about this <code>Realm</code> implementation. 069 */ 070 protected static final String name = "TomcatGeronimoRealm"; 071 072 public TomcatGeronimoRealm() { 073 074 } 075 076 public static String setRequestWrapperName(String requestWrapperName) { 077 String old = (String) currentRequestWrapperName.get(); 078 currentRequestWrapperName.set(requestWrapperName); 079 return old; 080 } 081 082 /** 083 * Enforce any user data constraint required by the security constraint 084 * guarding this request URI. Return <code>true</code> if this constraint 085 * was not violated and processing should continue, or <code>false</code> 086 * if we have created a response already. 087 * 088 * @param request Request we are processing 089 * @param response Response we are creating 090 * @param constraints Security constraint being checked 091 * @throws IOException if an input/output error occurs 092 */ 093 public boolean hasUserDataPermission(Request request, 094 Response response, 095 SecurityConstraint[] constraints) 096 throws IOException { 097 098 //Get an authenticated subject, if there is one 099 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 }