001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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 java.io.IOException; 020 import java.security.AccessControlContext; 021 import java.security.AccessControlException; 022 import java.security.Principal; 023 import java.security.cert.X509Certificate; 024 025 import javax.security.auth.Subject; 026 import javax.security.auth.callback.CallbackHandler; 027 import javax.security.auth.login.AccountExpiredException; 028 import javax.security.auth.login.CredentialExpiredException; 029 import javax.security.auth.login.FailedLoginException; 030 import javax.security.auth.login.LoginContext; 031 import javax.security.auth.login.LoginException; 032 import javax.security.jacc.PolicyContext; 033 import javax.security.jacc.PolicyContextException; 034 import javax.security.jacc.WebResourcePermission; 035 import javax.security.jacc.WebRoleRefPermission; 036 import javax.security.jacc.WebUserDataPermission; 037 038 import org.apache.catalina.Context; 039 import org.apache.catalina.LifecycleException; 040 import org.apache.catalina.connector.Request; 041 import org.apache.catalina.connector.Response; 042 import org.apache.catalina.deploy.LoginConfig; 043 import org.apache.catalina.deploy.SecurityConstraint; 044 import org.apache.catalina.realm.JAASRealm; 045 import org.apache.commons.logging.Log; 046 import org.apache.commons.logging.LogFactory; 047 import org.apache.geronimo.security.ContextManager; 048 import org.apache.geronimo.security.jacc.PolicyContextHandlerContainerSubject; 049 import org.apache.geronimo.security.realm.providers.CertificateChainCallbackHandler; 050 import org.apache.geronimo.security.realm.providers.PasswordCallbackHandler; 051 import org.apache.geronimo.tomcat.JAASTomcatPrincipal; 052 import org.apache.geronimo.tomcat.interceptor.PolicyContextBeforeAfter; 053 054 055 public class TomcatGeronimoRealm extends JAASRealm { 056 057 private static final Log log = LogFactory.getLog(TomcatGeronimoRealm.class); 058 059 private static ThreadLocal<String> currentRequestWrapperName = new ThreadLocal<String>(); 060 061 /** 062 * Descriptive information about this <code>Realm</code> implementation. 063 */ 064 protected static final String info = "org.apache.geronimo.tomcat.TomcatGeronimoRealm/1.0"; 065 066 /** 067 * Descriptive information about this <code>Realm</code> implementation. 068 */ 069 protected static final String name = "TomcatGeronimoRealm"; 070 071 public TomcatGeronimoRealm() { 072 073 } 074 075 public static String setRequestWrapperName(String requestWrapperName) { 076 String old = currentRequestWrapperName.get(); 077 currentRequestWrapperName.set(requestWrapperName); 078 return old; 079 } 080 081 /** 082 * Enforce any user data constraint required by the security constraint 083 * guarding this request URI. Return <code>true</code> if this constraint 084 * was not violated and processing should continue, or <code>false</code> 085 * if we have created a response already. 086 * 087 * @param request Request we are processing 088 * @param response Response we are creating 089 * @param constraints Security constraint being checked 090 * @throws IOException if an input/output error occurs 091 */ 092 public boolean hasUserDataPermission(Request request, 093 Response response, 094 SecurityConstraint[] constraints) 095 throws IOException { 096 097 //Get an authenticated subject, if there is one 098 Subject subject = null; 099 try { 100 101 //We will use the PolicyContextHandlerContainerSubject.HANDLER_KEY to see if a user 102 //has authenticated, since a request.getUserPrincipal() will not pick up the user 103 //unless its using a cached session. 104 subject = (Subject) PolicyContext.getContext(PolicyContextHandlerContainerSubject.HANDLER_KEY); 105 106 } catch (PolicyContextException e) { 107 log.error(e); 108 } 109 110 //If nothing has authenticated yet, do the normal 111 if (subject == null) 112 return super.hasUserDataPermission(request, response, constraints); 113 114 ContextManager.setCallers(subject, subject); 115 116 try { 117 118 AccessControlContext acc = ContextManager.getCurrentContext(); 119 120 /** 121 * JACC v1.0 secion 4.1.1 122 */ 123 WebUserDataPermission wudp = new WebUserDataPermission(request); 124 acc.checkPermission(wudp); 125 126 } catch (AccessControlException ace) { 127 response.sendError(Response.SC_FORBIDDEN); 128 return false; 129 } 130 131 return true; 132 } 133 134 /** 135 * Perform access control based on the specified authorization constraint. 136 * Return <code>true</code> if this constraint is satisfied and processing 137 * should continue, or <code>false</code> otherwise. 138 * 139 * @param request Request we are processing 140 * @param response Response we are creating 141 * @param constraints Security constraints we are enforcing 142 * @param context The Context to which client of this class is attached. 143 * @throws java.io.IOException if an input/output error occurs 144 */ 145 public boolean hasResourcePermission(Request request, 146 Response response, 147 SecurityConstraint[] constraints, 148 Context context) 149 throws IOException { 150 151 // Specifically allow access to the form login and form error pages 152 // and the "j_security_check" action 153 LoginConfig config = context.getLoginConfig(); 154 if ((config != null) && 155 (org.apache.catalina.realm.Constants.FORM_METHOD.equals(config.getAuthMethod()))) { 156 String requestURI = request.getDecodedRequestURI(); 157 String loginPage = context.getPath() + config.getLoginPage(); 158 if (loginPage.equals(requestURI)) { 159 if (log.isDebugEnabled()) 160 log.debug(" Allow access to login page " + loginPage); 161 return (true); 162 } 163 String errorPage = context.getPath() + config.getErrorPage(); 164 if (errorPage.equals(requestURI)) { 165 if (log.isDebugEnabled()) 166 log.debug(" Allow access to error page " + errorPage); 167 return (true); 168 } 169 if (requestURI.endsWith(org.apache.catalina.realm.Constants.FORM_ACTION)) { 170 if (log.isDebugEnabled()) 171 log.debug(" Allow access to username/password submission"); 172 return (true); 173 } 174 } 175 176 //Set the current wrapper name (Servlet mapping) 177 currentRequestWrapperName.set(request.getWrapper().getName()); 178 179 // Which user principal have we already authenticated? 180 Principal principal = request.getUserPrincipal(); 181 182 //If we have no principal, then we should use the default. 183 if (principal == null) { 184 Subject defaultSubject = (Subject) request.getAttribute(PolicyContextBeforeAfter.DEFAULT_SUBJECT); 185 ContextManager.setCallers(defaultSubject, defaultSubject); 186 } else { 187 Subject currentCaller = ((JAASTomcatPrincipal) principal).getSubject(); 188 ContextManager.setCallers(currentCaller, currentCaller); 189 } 190 191 try { 192 193 AccessControlContext acc = ContextManager.getCurrentContext(); 194 195 /** 196 * JACC v1.0 section 4.1.2 197 */ 198 acc.checkPermission(new WebResourcePermission(request)); 199 200 } catch (AccessControlException ace) { 201 response.sendError(Response.SC_FORBIDDEN); 202 return false; 203 } 204 205 return true; 206 207 } 208 209 /** 210 * Return <code>true</code> if the specified Principal has the specified 211 * security role, within the context of this Realm; otherwise return 212 * <code>false</code>. 213 * 214 * @param principal Principal for whom the role is to be checked 215 * @param role Security role to be checked 216 */ 217 public boolean hasRole(Principal principal, String role) { 218 219 if ((principal == null) || (role == null) || !(principal instanceof JAASTomcatPrincipal)) { 220 return false; 221 } 222 223 String name = currentRequestWrapperName.get(); 224 225 /** 226 * JACC v1.0 secion B.19 227 */ 228 if (name == null || name.equals("jsp")) { 229 name = ""; 230 } 231 232 //Set the caller 233 Subject currentCaller = ((JAASTomcatPrincipal) principal).getSubject(); 234 ContextManager.setCallers(currentCaller, currentCaller); 235 236 AccessControlContext acc = ContextManager.getCurrentContext(); 237 238 try { 239 /** 240 * JACC v1.0 section 4.1.3 241 */ 242 acc.checkPermission(new WebRoleRefPermission(name, role)); 243 } catch (AccessControlException e) { 244 return false; 245 } 246 247 return true; 248 } 249 250 /** 251 * Return the <code>Principal</code> associated with the specified 252 * username and credentials, if there is one; otherwise return 253 * <code>null</code>. 254 * <p/> 255 * If there are any errors with the JDBC connection, executing the query or 256 * anything we return null (don't authenticate). This event is also logged, 257 * and the connection will be closed so that a subsequent request will 258 * automatically re-open it. 259 * 260 * @param username Username of the <code>Principal</code> to look up 261 * @param credentials Password or other credentials to use in authenticating this 262 * username 263 */ 264 public Principal authenticate(String username, String credentials) { 265 266 char[] cred = credentials == null ? null : credentials.toCharArray(); 267 CallbackHandler callbackHandler = new PasswordCallbackHandler(username, cred); 268 return authenticate(callbackHandler, username); 269 } 270 271 public Principal authenticate(X509Certificate[] certs) { 272 if (certs == null || certs.length == 0) { 273 return null; 274 } 275 CallbackHandler callbackHandler = new CertificateChainCallbackHandler(certs); 276 String principalName = certs[0].getSubjectX500Principal().getName(); 277 return authenticate(callbackHandler, principalName); 278 } 279 280 public Principal authenticate(CallbackHandler callbackHandler, String principalName) { 281 282 // Establish a LoginContext to use for authentication 283 try { 284 285 if ((principalName != null) && (!principalName.equals(""))) { 286 LoginContext loginContext = null; 287 if (appName == null) 288 appName = "Tomcat"; 289 290 if (log.isDebugEnabled()) 291 log.debug(sm.getString("jaasRealm.beginLogin", principalName, appName)); 292 293 // What if the LoginModule is in the container class loader ? 294 ClassLoader ocl = null; 295 296 if (isUseContextClassLoader()) { 297 ocl = Thread.currentThread().getContextClassLoader(); 298 Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); 299 } 300 301 try { 302 loginContext = ContextManager.login(appName, callbackHandler); 303 } catch (AccountExpiredException e) { 304 if (log.isDebugEnabled()) 305 log.debug(sm.getString("jaasRealm.accountExpired", principalName)); 306 return (null); 307 } catch (CredentialExpiredException e) { 308 if (log.isDebugEnabled()) 309 log.debug(sm.getString("jaasRealm.credentialExpired", principalName)); 310 return (null); 311 } catch (FailedLoginException e) { 312 if (log.isDebugEnabled()) 313 log.debug(sm.getString("jaasRealm.failedLogin", principalName)); 314 return (null); 315 } catch (LoginException e) { 316 log.warn(sm.getString("jaasRealm.loginException", principalName), e); 317 return (null); 318 } catch (Throwable e) { 319 log.error(sm.getString("jaasRealm.unexpectedError"), e); 320 return (null); 321 } finally { 322 if (isUseContextClassLoader()) { 323 Thread.currentThread().setContextClassLoader(ocl); 324 } 325 } 326 327 if (log.isDebugEnabled()) 328 log.debug("Login context created " + principalName); 329 330 // Negotiate a login via this LoginContext 331 Subject subject = loginContext.getSubject(); 332 ContextManager.setCallers(subject, subject); 333 334 if (log.isDebugEnabled()) 335 log.debug(sm.getString("jaasRealm.loginContextCreated", principalName)); 336 337 // Return the appropriate Principal for this authenticated Subject 338 JAASTomcatPrincipal jaasPrincipal = new JAASTomcatPrincipal(principalName); 339 jaasPrincipal.setSubject(subject); 340 341 return (jaasPrincipal); 342 } else { 343 if (log.isDebugEnabled()) 344 log.debug("Login Failed - null userID"); 345 return null; 346 } 347 348 } catch (Throwable t) { 349 log.error("error ", t); 350 return null; 351 } 352 } 353 354 /** 355 * Prepare for active use of the public methods of this <code>Component</code>. 356 * 357 * @throws org.apache.catalina.LifecycleException 358 * if this component detects a fatal error 359 * that prevents it from being started 360 */ 361 public void start() throws LifecycleException { 362 363 // Perform normal superclass initialization 364 super.start(); 365 setUseContextClassLoader(false); 366 } 367 368 /** 369 * Gracefully shut down active use of the public methods of this <code>Component</code>. 370 * 371 * @throws LifecycleException if this component detects a fatal error 372 * that needs to be reported 373 */ 374 public void stop() throws LifecycleException { 375 376 // Perform normal superclass finalization 377 super.stop(); 378 379 } 380 }