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 }