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