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 String name = currentRequestWrapperName.get(); 220 221 /** 222 * JACC v1.0 secion B.19 223 */ 224 if (name == null || name.equals("jsp")) { 225 name = ""; 226 } 227 228 AccessControlContext acc = ContextManager.getCurrentContext(); 229 230 try { 231 /** 232 * JACC v1.0 section 4.1.3 233 */ 234 acc.checkPermission(new WebRoleRefPermission(name, role)); 235 } catch (AccessControlException e) { 236 return false; 237 } 238 239 return true; 240 } 241 242 /** 243 * Return the <code>Principal</code> associated with the specified 244 * username and credentials, if there is one; otherwise return 245 * <code>null</code>. 246 * <p/> 247 * If there are any errors with the JDBC connection, executing the query or 248 * anything we return null (don't authenticate). This event is also logged, 249 * and the connection will be closed so that a subsequent request will 250 * automatically re-open it. 251 * 252 * @param username Username of the <code>Principal</code> to look up 253 * @param credentials Password or other credentials to use in authenticating this 254 * username 255 */ 256 public Principal authenticate(String username, String credentials) { 257 258 char[] cred = credentials == null ? null : credentials.toCharArray(); 259 CallbackHandler callbackHandler = new PasswordCallbackHandler(username, cred); 260 return authenticate(callbackHandler, username); 261 } 262 263 public Principal authenticate(X509Certificate[] certs) { 264 if (certs == null || certs.length == 0) { 265 return null; 266 } 267 CallbackHandler callbackHandler = new CertificateChainCallbackHandler(certs); 268 String principalName = certs[0].getSubjectX500Principal().getName(); 269 return authenticate(callbackHandler, principalName); 270 } 271 272 public Principal authenticate(CallbackHandler callbackHandler, String principalName) { 273 274 // Establish a LoginContext to use for authentication 275 try { 276 277 if ((principalName != null) && (!principalName.equals(""))) { 278 LoginContext loginContext = null; 279 if (appName == null) 280 appName = "Tomcat"; 281 282 if (log.isDebugEnabled()) 283 log.debug(sm.getString("jaasRealm.beginLogin", principalName, appName)); 284 285 // What if the LoginModule is in the container class loader ? 286 ClassLoader ocl = null; 287 288 if (isUseContextClassLoader()) { 289 ocl = Thread.currentThread().getContextClassLoader(); 290 Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); 291 } 292 293 try { 294 loginContext = ContextManager.login(appName, callbackHandler); 295 } catch (AccountExpiredException e) { 296 if (log.isDebugEnabled()) 297 log.debug(sm.getString("jaasRealm.accountExpired", principalName)); 298 return (null); 299 } catch (CredentialExpiredException e) { 300 if (log.isDebugEnabled()) 301 log.debug(sm.getString("jaasRealm.credentialExpired", principalName)); 302 return (null); 303 } catch (FailedLoginException e) { 304 if (log.isDebugEnabled()) 305 log.debug(sm.getString("jaasRealm.failedLogin", principalName)); 306 return (null); 307 } catch (LoginException e) { 308 log.warn(sm.getString("jaasRealm.loginException", principalName), e); 309 return (null); 310 } catch (Throwable e) { 311 log.error(sm.getString("jaasRealm.unexpectedError"), e); 312 return (null); 313 } finally { 314 if (isUseContextClassLoader()) { 315 Thread.currentThread().setContextClassLoader(ocl); 316 } 317 } 318 319 if (log.isDebugEnabled()) 320 log.debug("Login context created " + principalName); 321 322 // Negotiate a login via this LoginContext 323 Subject subject = loginContext.getSubject(); 324 ContextManager.setCallers(subject, subject); 325 326 if (log.isDebugEnabled()) 327 log.debug(sm.getString("jaasRealm.loginContextCreated", principalName)); 328 329 // Return the appropriate Principal for this authenticated Subject 330 JAASTomcatPrincipal jaasPrincipal = new JAASTomcatPrincipal(principalName); 331 jaasPrincipal.setSubject(subject); 332 333 return (jaasPrincipal); 334 } else { 335 if (log.isDebugEnabled()) 336 log.debug("Login Failed - null userID"); 337 return null; 338 } 339 340 } catch (Throwable t) { 341 log.error("error ", t); 342 return null; 343 } 344 } 345 346 /** 347 * Prepare for active use of the public methods of this <code>Component</code>. 348 * 349 * @throws org.apache.catalina.LifecycleException 350 * if this component detects a fatal error 351 * that prevents it from being started 352 */ 353 public void start() throws LifecycleException { 354 355 // Perform normal superclass initialization 356 super.start(); 357 setUseContextClassLoader(false); 358 } 359 360 /** 361 * Gracefully shut down active use of the public methods of this <code>Component</code>. 362 * 363 * @throws LifecycleException if this component detects a fatal error 364 * that needs to be reported 365 */ 366 public void stop() throws LifecycleException { 367 368 // Perform normal superclass finalization 369 super.stop(); 370 371 } 372 }