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 }