0001: /*
0002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/authenticator/AuthenticatorBase.java,v 1.32 2002/06/09 02:19:41 remm Exp $
0003: * $Revision: 1.32 $
0004: * $Date: 2002/06/09 02:19:41 $
0005: *
0006: * ====================================================================
0007: *
0008: * The Apache Software License, Version 1.1
0009: *
0010: * Copyright (c) 1999-2001 The Apache Software Foundation. All rights
0011: * reserved.
0012: *
0013: * Redistribution and use in source and binary forms, with or without
0014: * modification, are permitted provided that the following conditions
0015: * are met:
0016: *
0017: * 1. Redistributions of source code must retain the above copyright
0018: * notice, this list of conditions and the following disclaimer.
0019: *
0020: * 2. Redistributions in binary form must reproduce the above copyright
0021: * notice, this list of conditions and the following disclaimer in
0022: * the documentation and/or other materials provided with the
0023: * distribution.
0024: *
0025: * 3. The end-user documentation included with the redistribution, if
0026: * any, must include the following acknowlegement:
0027: * "This product includes software developed by the
0028: * Apache Software Foundation (http://www.apache.org/)."
0029: * Alternately, this acknowlegement may appear in the software itself,
0030: * if and wherever such third-party acknowlegements normally appear.
0031: *
0032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
0033: * Foundation" must not be used to endorse or promote products derived
0034: * from this software without prior written permission. For written
0035: * permission, please contact apache@apache.org.
0036: *
0037: * 5. Products derived from this software may not be called "Apache"
0038: * nor may "Apache" appear in their names without prior written
0039: * permission of the Apache Group.
0040: *
0041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0052: * SUCH DAMAGE.
0053: * ====================================================================
0054: *
0055: * This software consists of voluntary contributions made by many
0056: * individuals on behalf of the Apache Software Foundation. For more
0057: * information on the Apache Software Foundation, please see
0058: * <http://www.apache.org/>.
0059: *
0060: * [Additional notices, if required by prior licensing conditions]
0061: *
0062: */
0063:
0064: package org.apache.catalina.authenticator;
0065:
0066: import java.io.IOException;
0067: import java.lang.reflect.Method;
0068: import java.net.MalformedURLException;
0069: import java.net.URL;
0070: import java.security.MessageDigest;
0071: import java.security.NoSuchAlgorithmException;
0072: import java.security.Principal;
0073: import java.util.Random;
0074: import javax.servlet.ServletException;
0075: import javax.servlet.http.Cookie;
0076: import javax.servlet.http.HttpServletRequest;
0077: import javax.servlet.http.HttpServletResponse;
0078: import javax.servlet.http.HttpSession;
0079: import org.apache.catalina.Authenticator;
0080: import org.apache.catalina.Container;
0081: import org.apache.catalina.Context;
0082: import org.apache.catalina.HttpRequest;
0083: import org.apache.catalina.HttpResponse;
0084: import org.apache.catalina.Lifecycle;
0085: import org.apache.catalina.LifecycleEvent;
0086: import org.apache.catalina.LifecycleException;
0087: import org.apache.catalina.LifecycleListener;
0088: import org.apache.catalina.Logger;
0089: import org.apache.catalina.Manager;
0090: import org.apache.catalina.Pipeline;
0091: import org.apache.catalina.Realm;
0092: import org.apache.catalina.Request;
0093: import org.apache.catalina.Response;
0094: import org.apache.catalina.Session;
0095: import org.apache.catalina.Valve;
0096: import org.apache.catalina.ValveContext;
0097: import org.apache.catalina.deploy.LoginConfig;
0098: import org.apache.catalina.deploy.SecurityConstraint;
0099: import org.apache.catalina.util.LifecycleSupport;
0100: import org.apache.catalina.util.RequestUtil;
0101: import org.apache.catalina.util.StringManager;
0102: import org.apache.catalina.valves.ValveBase;
0103:
0104: /**
0105: * Basic implementation of the <b>Valve</b> interface that enforces the
0106: * <code><security-constraint></code> elements in the web application
0107: * deployment descriptor. This functionality is implemented as a Valve
0108: * so that it can be ommitted in environments that do not require these
0109: * features. Individual implementations of each supported authentication
0110: * method can subclass this base class as required.
0111: * <p>
0112: * <b>USAGE CONSTRAINT</b>: When this class is utilized, the Context to
0113: * which it is attached (or a parent Container in a hierarchy) must have an
0114: * associated Realm that can be used for authenticating users and enumerating
0115: * the roles to which they have been assigned.
0116: * <p>
0117: * <b>USAGE CONSTRAINT</b>: This Valve is only useful when processing HTTP
0118: * requests. Requests of any other type will simply be passed through.
0119: *
0120: * @author Craig R. McClanahan
0121: * @version $Revision: 1.32 $ $Date: 2002/06/09 02:19:41 $
0122: */
0123:
0124: public abstract class AuthenticatorBase extends ValveBase implements
0125: Authenticator, Lifecycle {
0126:
0127: // ----------------------------------------------------- Instance Variables
0128:
0129: /**
0130: * The default message digest algorithm to use if we cannot use
0131: * the requested one.
0132: */
0133: protected static final String DEFAULT_ALGORITHM = "MD5";
0134:
0135: /**
0136: * The number of random bytes to include when generating a
0137: * session identifier.
0138: */
0139: protected static final int SESSION_ID_BYTES = 16;
0140:
0141: /**
0142: * The message digest algorithm to be used when generating session
0143: * identifiers. This must be an algorithm supported by the
0144: * <code>java.security.MessageDigest</code> class on your platform.
0145: */
0146: protected String algorithm = DEFAULT_ALGORITHM;
0147:
0148: /**
0149: * Should we cache authenticated Principals if the request is part of
0150: * an HTTP session?
0151: */
0152: protected boolean cache = true;
0153:
0154: /**
0155: * The Context to which this Valve is attached.
0156: */
0157: protected Context context = null;
0158:
0159: /**
0160: * The debugging detail level for this component.
0161: */
0162: protected int debug = 0;
0163:
0164: /**
0165: * Return the MessageDigest implementation to be used when
0166: * creating session identifiers.
0167: */
0168: protected MessageDigest digest = null;
0169:
0170: /**
0171: * A String initialization parameter used to increase the entropy of
0172: * the initialization of our random number generator.
0173: */
0174: protected String entropy = null;
0175:
0176: /**
0177: * Descriptive information about this implementation.
0178: */
0179: protected static final String info = "org.apache.catalina.authenticator.AuthenticatorBase/1.0";
0180:
0181: /**
0182: * The lifecycle event support for this component.
0183: */
0184: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
0185:
0186: /**
0187: * A random number generator to use when generating session identifiers.
0188: */
0189: protected Random random = null;
0190:
0191: /**
0192: * The Java class name of the random number generator class to be used
0193: * when generating session identifiers.
0194: */
0195: protected String randomClass = "java.security.SecureRandom";
0196:
0197: /**
0198: * The string manager for this package.
0199: */
0200: protected static final StringManager sm = StringManager
0201: .getManager(Constants.Package);
0202:
0203: /**
0204: * The SingleSignOn implementation in our request processing chain,
0205: * if there is one.
0206: */
0207: protected SingleSignOn sso = null;
0208:
0209: /**
0210: * Has this component been started?
0211: */
0212: protected boolean started = false;
0213:
0214: // ------------------------------------------------------------- Properties
0215:
0216: /**
0217: * Return the message digest algorithm for this Manager.
0218: */
0219: public String getAlgorithm() {
0220:
0221: return (this .algorithm);
0222:
0223: }
0224:
0225: /**
0226: * Set the message digest algorithm for this Manager.
0227: *
0228: * @param algorithm The new message digest algorithm
0229: */
0230: public void setAlgorithm(String algorithm) {
0231:
0232: this .algorithm = algorithm;
0233:
0234: }
0235:
0236: /**
0237: * Return the cache authenticated Principals flag.
0238: */
0239: public boolean getCache() {
0240:
0241: return (this .cache);
0242:
0243: }
0244:
0245: /**
0246: * Set the cache authenticated Principals flag.
0247: *
0248: * @param cache The new cache flag
0249: */
0250: public void setCache(boolean cache) {
0251:
0252: this .cache = cache;
0253:
0254: }
0255:
0256: /**
0257: * Return the Container to which this Valve is attached.
0258: */
0259: public Container getContainer() {
0260:
0261: return (this .context);
0262:
0263: }
0264:
0265: /**
0266: * Set the Container to which this Valve is attached.
0267: *
0268: * @param container The container to which we are attached
0269: */
0270: public void setContainer(Container container) {
0271:
0272: if (!(container instanceof Context))
0273: throw new IllegalArgumentException(sm
0274: .getString("authenticator.notContext"));
0275:
0276: super .setContainer(container);
0277: this .context = (Context) container;
0278:
0279: }
0280:
0281: /**
0282: * Return the debugging detail level for this component.
0283: */
0284: public int getDebug() {
0285:
0286: return (this .debug);
0287:
0288: }
0289:
0290: /**
0291: * Set the debugging detail level for this component.
0292: *
0293: * @param debug The new debugging detail level
0294: */
0295: public void setDebug(int debug) {
0296:
0297: this .debug = debug;
0298:
0299: }
0300:
0301: /**
0302: * Return the entropy increaser value, or compute a semi-useful value
0303: * if this String has not yet been set.
0304: */
0305: public String getEntropy() {
0306:
0307: // Calculate a semi-useful value if this has not been set
0308: if (this .entropy == null)
0309: setEntropy(this .toString());
0310:
0311: return (this .entropy);
0312:
0313: }
0314:
0315: /**
0316: * Set the entropy increaser value.
0317: *
0318: * @param entropy The new entropy increaser value
0319: */
0320: public void setEntropy(String entropy) {
0321:
0322: this .entropy = entropy;
0323:
0324: }
0325:
0326: /**
0327: * Return descriptive information about this Valve implementation.
0328: */
0329: public String getInfo() {
0330:
0331: return (this .info);
0332:
0333: }
0334:
0335: /**
0336: * Return the random number generator class name.
0337: */
0338: public String getRandomClass() {
0339:
0340: return (this .randomClass);
0341:
0342: }
0343:
0344: /**
0345: * Set the random number generator class name.
0346: *
0347: * @param randomClass The new random number generator class name
0348: */
0349: public void setRandomClass(String randomClass) {
0350:
0351: this .randomClass = randomClass;
0352:
0353: }
0354:
0355: // --------------------------------------------------------- Public Methods
0356:
0357: /**
0358: * Enforce the security restrictions in the web application deployment
0359: * descriptor of our associated Context.
0360: *
0361: * @param request Request to be processed
0362: * @param response Response to be processed
0363: * @param context The valve context used to invoke the next valve
0364: * in the current processing pipeline
0365: *
0366: * @exception IOException if an input/output error occurs
0367: * @exception ServletException if thrown by a processing element
0368: */
0369: public void invoke(Request request, Response response,
0370: ValveContext context) throws IOException, ServletException {
0371:
0372: // If this is not an HTTP request, do nothing
0373: if (!(request instanceof HttpRequest)
0374: || !(response instanceof HttpResponse)) {
0375: context.invokeNext(request, response);
0376: return;
0377: }
0378: if (!(request.getRequest() instanceof HttpServletRequest)
0379: || !(response.getResponse() instanceof HttpServletResponse)) {
0380: context.invokeNext(request, response);
0381: return;
0382: }
0383: HttpRequest hrequest = (HttpRequest) request;
0384: HttpResponse hresponse = (HttpResponse) response;
0385: if (debug >= 1)
0386: log("Security checking request "
0387: + ((HttpServletRequest) request.getRequest())
0388: .getMethod()
0389: + " "
0390: + ((HttpServletRequest) request.getRequest())
0391: .getRequestURI());
0392: LoginConfig config = this .context.getLoginConfig();
0393:
0394: // Have we got a cached authenticated Principal to record?
0395: if (cache) {
0396: Principal principal = ((HttpServletRequest) request
0397: .getRequest()).getUserPrincipal();
0398: if (principal == null) {
0399: Session session = getSession(hrequest);
0400: if (session != null) {
0401: principal = session.getPrincipal();
0402: if (principal != null) {
0403: if (debug >= 1)
0404: log("We have cached auth type "
0405: + session.getAuthType()
0406: + " for principal "
0407: + session.getPrincipal());
0408: hrequest.setAuthType(session.getAuthType());
0409: hrequest.setUserPrincipal(principal);
0410: }
0411: }
0412: }
0413: }
0414:
0415: // Special handling for form-based logins to deal with the case
0416: // where the login form (and therefore the "j_security_check" URI
0417: // to which it submits) might be outside the secured area
0418: String contextPath = this .context.getPath();
0419: String requestURI = hrequest.getDecodedRequestURI();
0420: if (requestURI.startsWith(contextPath)
0421: && requestURI.endsWith(Constants.FORM_ACTION)) {
0422: if (!authenticate(hrequest, hresponse, config)) {
0423: if (debug >= 1)
0424: log(" Failed authenticate() test");
0425: return;
0426: }
0427: }
0428:
0429: // Is this request URI subject to a security constraint?
0430: SecurityConstraint constraint = findConstraint(hrequest);
0431: if ((constraint == null) /* &&
0432: (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */) {
0433: if (debug >= 1)
0434: log(" Not subject to any constraint");
0435: context.invokeNext(request, response);
0436: return;
0437: }
0438: if ((debug >= 1) && (constraint != null))
0439: log(" Subject to constraint " + constraint);
0440:
0441: // Make sure that constrained resources are not cached by web proxies
0442: // or browsers as caching can provide a security hole
0443: if (!(((HttpServletRequest) hrequest.getRequest()).isSecure())) {
0444: HttpServletResponse sresponse = (HttpServletResponse) response
0445: .getResponse();
0446: sresponse.setHeader("Pragma", "No-cache");
0447: sresponse.setHeader("Cache-Control", "no-cache");
0448: sresponse.setDateHeader("Expires", 1);
0449: }
0450:
0451: // Enforce any user data constraint for this security constraint
0452: if (debug >= 1)
0453: log(" Calling checkUserData()");
0454: if (!checkUserData(hrequest, hresponse, constraint)) {
0455: if (debug >= 1)
0456: log(" Failed checkUserData() test");
0457: // ASSERT: Authenticator already set the appropriate
0458: // HTTP status code, so we do not have to do anything special
0459: return;
0460: }
0461:
0462: // Authenticate based upon the specified login configuration
0463: if (constraint.getAuthConstraint()) {
0464: if (debug >= 1)
0465: log(" Calling authenticate()");
0466: if (!authenticate(hrequest, hresponse, config)) {
0467: if (debug >= 1)
0468: log(" Failed authenticate() test");
0469: // ASSERT: Authenticator already set the appropriate
0470: // HTTP status code, so we do not have to do anything special
0471: return;
0472: }
0473: }
0474:
0475: // Perform access control based on the specified role(s)
0476: if (constraint.getAuthConstraint()) {
0477: if (debug >= 1)
0478: log(" Calling accessControl()");
0479: if (!accessControl(hrequest, hresponse, constraint)) {
0480: if (debug >= 1)
0481: log(" Failed accessControl() test");
0482: // ASSERT: AccessControl method has already set the appropriate
0483: // HTTP status code, so we do not have to do anything special
0484: return;
0485: }
0486: }
0487:
0488: // Any and all specified constraints have been satisfied
0489: if (debug >= 1)
0490: log(" Successfully passed all security constraints");
0491: context.invokeNext(request, response);
0492:
0493: }
0494:
0495: // ------------------------------------------------------ Protected Methods
0496:
0497: /**
0498: * Perform access control based on the specified authorization constraint.
0499: * Return <code>true</code> if this constraint is satisfied and processing
0500: * should continue, or <code>false</code> otherwise.
0501: *
0502: * @param request Request we are processing
0503: * @param response Response we are creating
0504: * @param constraint Security constraint we are enforcing
0505: *
0506: * @exception IOException if an input/output error occurs
0507: */
0508: protected boolean accessControl(HttpRequest request,
0509: HttpResponse response, SecurityConstraint constraint)
0510: throws IOException {
0511:
0512: if (constraint == null)
0513: return (true);
0514:
0515: // Specifically allow access to the form login and form error pages
0516: // and the "j_security_check" action
0517: LoginConfig config = context.getLoginConfig();
0518: if ((config != null)
0519: && (Constants.FORM_METHOD
0520: .equals(config.getAuthMethod()))) {
0521: String requestURI = request.getDecodedRequestURI();
0522: String loginPage = context.getPath()
0523: + config.getLoginPage();
0524: if (loginPage.equals(requestURI)) {
0525: if (debug >= 1)
0526: log(" Allow access to login page " + loginPage);
0527: return (true);
0528: }
0529: String errorPage = context.getPath()
0530: + config.getErrorPage();
0531: if (errorPage.equals(requestURI)) {
0532: if (debug >= 1)
0533: log(" Allow access to error page " + errorPage);
0534: return (true);
0535: }
0536: if (requestURI.endsWith(Constants.FORM_ACTION)) {
0537: if (debug >= 1)
0538: log(" Allow access to username/password submission");
0539: return (true);
0540: }
0541: }
0542:
0543: // Which user principal have we already authenticated?
0544: Principal principal = ((HttpServletRequest) request
0545: .getRequest()).getUserPrincipal();
0546: if (principal == null) {
0547: if (debug >= 2)
0548: log(" No user authenticated, cannot grant access");
0549: ((HttpServletResponse) response.getResponse()).sendError(
0550: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
0551: sm.getString("authenticator.notAuthenticated"));
0552: return (false);
0553: }
0554:
0555: // Check each role included in this constraint
0556: Realm realm = context.getRealm();
0557: String roles[] = constraint.findAuthRoles();
0558: if (roles == null)
0559: roles = new String[0];
0560:
0561: if (constraint.getAllRoles())
0562: return (true);
0563: if ((roles.length == 0) && (constraint.getAuthConstraint())) {
0564: ((HttpServletResponse) response.getResponse()).sendError(
0565: HttpServletResponse.SC_FORBIDDEN, sm
0566: .getString("authenticator.forbidden"));
0567: return (false); // No listed roles means no access at all
0568: }
0569: for (int i = 0; i < roles.length; i++) {
0570: if (realm.hasRole(principal, roles[i]))
0571: return (true);
0572: }
0573:
0574: // Return a "Forbidden" message denying access to this resource
0575: ((HttpServletResponse) response.getResponse()).sendError(
0576: HttpServletResponse.SC_FORBIDDEN, sm
0577: .getString("authenticator.forbidden"));
0578: return (false);
0579:
0580: }
0581:
0582: /**
0583: * Associate the specified single sign on identifier with the
0584: * specified Session.
0585: *
0586: * @param ssoId Single sign on identifier
0587: * @param session Session to be associated
0588: */
0589: protected void associate(String ssoId, Session session) {
0590:
0591: if (sso == null)
0592: return;
0593: sso.associate(ssoId, session);
0594:
0595: }
0596:
0597: /**
0598: * Authenticate the user making this request, based on the specified
0599: * login configuration. Return <code>true</code> if any specified
0600: * constraint has been satisfied, or <code>false</code> if we have
0601: * created a response challenge already.
0602: *
0603: * @param request Request we are processing
0604: * @param response Response we are creating
0605: * @param login Login configuration describing how authentication
0606: * should be performed
0607: *
0608: * @exception IOException if an input/output error occurs
0609: */
0610: protected abstract boolean authenticate(HttpRequest request,
0611: HttpResponse response, LoginConfig config)
0612: throws IOException;
0613:
0614: /**
0615: * Enforce any user data constraint required by the security constraint
0616: * guarding this request URI. Return <code>true</code> if this constraint
0617: * was not violated and processing should continue, or <code>false</code>
0618: * if we have created a response already.
0619: *
0620: * @param request Request we are processing
0621: * @param response Response we are creating
0622: * @param constraint Security constraint being checked
0623: *
0624: * @exception IOException if an input/output error occurs
0625: */
0626: protected boolean checkUserData(HttpRequest request,
0627: HttpResponse response, SecurityConstraint constraint)
0628: throws IOException {
0629:
0630: // Is there a relevant user data constraint?
0631: if (constraint == null) {
0632: if (debug >= 2)
0633: log(" No applicable security constraint defined");
0634: return (true);
0635: }
0636: String userConstraint = constraint.getUserConstraint();
0637: if (userConstraint == null) {
0638: if (debug >= 2)
0639: log(" No applicable user data constraint defined");
0640: return (true);
0641: }
0642: if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
0643: if (debug >= 2)
0644: log(" User data constraint has no restrictions");
0645: return (true);
0646: }
0647:
0648: // Validate the request against the user data constraint
0649: if (request.getRequest().isSecure()) {
0650: if (debug >= 2)
0651: log(" User data constraint already satisfied");
0652: return (true);
0653: }
0654:
0655: // Initialize variables we need to determine the appropriate action
0656: HttpServletRequest hrequest = (HttpServletRequest) request
0657: .getRequest();
0658: HttpServletResponse hresponse = (HttpServletResponse) response
0659: .getResponse();
0660: int redirectPort = request.getConnector().getRedirectPort();
0661:
0662: // Is redirecting disabled?
0663: if (redirectPort <= 0) {
0664: if (debug >= 2)
0665: log(" SSL redirect is disabled");
0666: hresponse.sendError(HttpServletResponse.SC_FORBIDDEN,
0667: hrequest.getRequestURI());
0668: return (false);
0669: }
0670:
0671: // Redirect to the corresponding SSL port
0672: String protocol = "https";
0673: String host = hrequest.getServerName();
0674: StringBuffer file = new StringBuffer(hrequest.getRequestURI());
0675: String requestedSessionId = hrequest.getRequestedSessionId();
0676: if ((requestedSessionId != null)
0677: && hrequest.isRequestedSessionIdFromURL()) {
0678: file.append(";jsessionid=");
0679: file.append(requestedSessionId);
0680: }
0681: String queryString = hrequest.getQueryString();
0682: if (queryString != null) {
0683: file.append('?');
0684: file.append(queryString);
0685: }
0686: URL url = null;
0687: try {
0688: url = new URL(protocol, host, redirectPort, file.toString());
0689: if (debug >= 2)
0690: log(" Redirecting to " + url.toString());
0691: hresponse.sendRedirect(url.toString());
0692: return (false);
0693: } catch (MalformedURLException e) {
0694: if (debug >= 2)
0695: log(" Cannot create new URL", e);
0696: hresponse.sendError(
0697: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
0698: hrequest.getRequestURI());
0699: return (false);
0700: }
0701:
0702: }
0703:
0704: /**
0705: * Return the SecurityConstraint configured to guard the request URI for
0706: * this request, or <code>null</code> if there is no such constraint.
0707: *
0708: * @param request Request we are processing
0709: */
0710: protected SecurityConstraint findConstraint(HttpRequest request) {
0711:
0712: // Are there any defined security constraints?
0713: SecurityConstraint constraints[] = context.findConstraints();
0714: if ((constraints == null) || (constraints.length == 0)) {
0715: if (debug >= 2)
0716: log(" No applicable constraints defined");
0717: return (null);
0718: }
0719:
0720: // Check each defined security constraint
0721: HttpServletRequest hreq = (HttpServletRequest) request
0722: .getRequest();
0723: String uri = request.getDecodedRequestURI();
0724: String contextPath = hreq.getContextPath();
0725: if (contextPath.length() > 0)
0726: uri = uri.substring(contextPath.length());
0727: uri = RequestUtil.URLDecode(uri); // Before checking constraints
0728: String method = hreq.getMethod();
0729: for (int i = 0; i < constraints.length; i++) {
0730: if (debug >= 2)
0731: log(" Checking constraint '" + constraints[i]
0732: + "' against " + method + " " + uri + " --> "
0733: + constraints[i].included(uri, method));
0734: if (constraints[i].included(uri, method))
0735: return (constraints[i]);
0736: }
0737:
0738: // No applicable security constraint was found
0739: if (debug >= 2)
0740: log(" No applicable constraint located");
0741: return (null);
0742:
0743: }
0744:
0745: /**
0746: * Generate and return a new session identifier for the cookie that
0747: * identifies an SSO principal.
0748: */
0749: protected synchronized String generateSessionId() {
0750:
0751: // Generate a byte array containing a session identifier
0752: Random random = getRandom();
0753: byte bytes[] = new byte[SESSION_ID_BYTES];
0754: getRandom().nextBytes(bytes);
0755: bytes = getDigest().digest(bytes);
0756:
0757: // Render the result as a String of hexadecimal digits
0758: StringBuffer result = new StringBuffer();
0759: for (int i = 0; i < bytes.length; i++) {
0760: byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
0761: byte b2 = (byte) (bytes[i] & 0x0f);
0762: if (b1 < 10)
0763: result.append((char) ('0' + b1));
0764: else
0765: result.append((char) ('A' + (b1 - 10)));
0766: if (b2 < 10)
0767: result.append((char) ('0' + b2));
0768: else
0769: result.append((char) ('A' + (b2 - 10)));
0770: }
0771: return (result.toString());
0772:
0773: }
0774:
0775: /**
0776: * Return the MessageDigest object to be used for calculating
0777: * session identifiers. If none has been created yet, initialize
0778: * one the first time this method is called.
0779: */
0780: protected synchronized MessageDigest getDigest() {
0781:
0782: if (this .digest == null) {
0783: try {
0784: this .digest = MessageDigest.getInstance(algorithm);
0785: } catch (NoSuchAlgorithmException e) {
0786: try {
0787: this .digest = MessageDigest
0788: .getInstance(DEFAULT_ALGORITHM);
0789: } catch (NoSuchAlgorithmException f) {
0790: this .digest = null;
0791: }
0792: }
0793: }
0794:
0795: return (this .digest);
0796:
0797: }
0798:
0799: /**
0800: * Return the random number generator instance we should use for
0801: * generating session identifiers. If there is no such generator
0802: * currently defined, construct and seed a new one.
0803: */
0804: protected synchronized Random getRandom() {
0805:
0806: if (this .random == null) {
0807: try {
0808: Class clazz = Class.forName(randomClass);
0809: this .random = (Random) clazz.newInstance();
0810: long seed = System.currentTimeMillis();
0811: char entropy[] = getEntropy().toCharArray();
0812: for (int i = 0; i < entropy.length; i++) {
0813: long update = ((byte) entropy[i]) << ((i % 8) * 8);
0814: seed ^= update;
0815: }
0816: this .random.setSeed(seed);
0817: } catch (Exception e) {
0818: this .random = new java.util.Random();
0819: }
0820: }
0821:
0822: return (this .random);
0823:
0824: }
0825:
0826: /**
0827: * Return the internal Session that is associated with this HttpRequest,
0828: * or <code>null</code> if there is no such Session.
0829: *
0830: * @param request The HttpRequest we are processing
0831: */
0832: protected Session getSession(HttpRequest request) {
0833:
0834: return (getSession(request, false));
0835:
0836: }
0837:
0838: /**
0839: * Return the internal Session that is associated with this HttpRequest,
0840: * possibly creating a new one if necessary, or <code>null</code> if
0841: * there is no such session and we did not create one.
0842: *
0843: * @param request The HttpRequest we are processing
0844: * @param create Should we create a session if needed?
0845: */
0846: protected Session getSession(HttpRequest request, boolean create) {
0847:
0848: HttpServletRequest hreq = (HttpServletRequest) request
0849: .getRequest();
0850: HttpSession hses = hreq.getSession(create);
0851: if (hses == null)
0852: return (null);
0853: Manager manager = context.getManager();
0854: if (manager == null)
0855: return (null);
0856: else {
0857: try {
0858: return (manager.findSession(hses.getId()));
0859: } catch (IOException e) {
0860: return (null);
0861: }
0862: }
0863:
0864: }
0865:
0866: /**
0867: * Log a message on the Logger associated with our Container (if any).
0868: *
0869: * @param message Message to be logged
0870: */
0871: protected void log(String message) {
0872:
0873: Logger logger = context.getLogger();
0874: if (logger != null)
0875: logger.log("Authenticator[" + context.getPath() + "]: "
0876: + message);
0877: else
0878: System.out.println("Authenticator[" + context.getPath()
0879: + "]: " + message);
0880:
0881: }
0882:
0883: /**
0884: * Log a message on the Logger associated with our Container (if any).
0885: *
0886: * @param message Message to be logged
0887: * @param throwable Associated exception
0888: */
0889: protected void log(String message, Throwable throwable) {
0890:
0891: Logger logger = context.getLogger();
0892: if (logger != null)
0893: logger.log("Authenticator[" + context.getPath() + "]: "
0894: + message, throwable);
0895: else {
0896: System.out.println("Authenticator[" + context.getPath()
0897: + "]: " + message);
0898: throwable.printStackTrace(System.out);
0899: }
0900:
0901: }
0902:
0903: /**
0904: * Register an authenticated Principal and authentication type in our
0905: * request, in the current session (if there is one), and with our
0906: * SingleSignOn valve, if there is one. Set the appropriate cookie
0907: * to be returned.
0908: *
0909: * @param request The servlet request we are processing
0910: * @param response The servlet response we are generating
0911: * @param principal The authenticated Principal to be registered
0912: * @param authType The authentication type to be registered
0913: * @param username Username used to authenticate (if any)
0914: * @param password Password used to authenticate (if any)
0915: */
0916: protected void register(HttpRequest request, HttpResponse response,
0917: Principal principal, String authType, String username,
0918: String password) {
0919:
0920: if (debug >= 1)
0921: log("Authenticated '" + principal.getName()
0922: + "' with type '" + authType + "'");
0923:
0924: // Cache the authentication information in our request
0925: request.setAuthType(authType);
0926: request.setUserPrincipal(principal);
0927:
0928: // Cache the authentication information in our session, if any
0929: if (cache) {
0930: Session session = getSession(request, false);
0931: if (session != null) {
0932: session.setAuthType(authType);
0933: session.setPrincipal(principal);
0934: if (username != null)
0935: session.setNote(Constants.SESS_USERNAME_NOTE,
0936: username);
0937: else
0938: session.removeNote(Constants.SESS_USERNAME_NOTE);
0939: if (password != null)
0940: session.setNote(Constants.SESS_PASSWORD_NOTE,
0941: password);
0942: else
0943: session.removeNote(Constants.SESS_PASSWORD_NOTE);
0944: }
0945: }
0946:
0947: // Construct a cookie to be returned to the client
0948: if (sso == null)
0949: return;
0950: HttpServletRequest hreq = (HttpServletRequest) request
0951: .getRequest();
0952: HttpServletResponse hres = (HttpServletResponse) response
0953: .getResponse();
0954: String value = generateSessionId();
0955: Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE,
0956: value);
0957: cookie.setMaxAge(-1);
0958: cookie.setPath("/");
0959: hres.addCookie(cookie);
0960:
0961: // Register this principal with our SSO valve
0962: sso.register(value, principal, authType, username, password);
0963: request.setNote(Constants.REQ_SSOID_NOTE, value);
0964:
0965: }
0966:
0967: // ------------------------------------------------------ Lifecycle Methods
0968:
0969: /**
0970: * Add a lifecycle event listener to this component.
0971: *
0972: * @param listener The listener to add
0973: */
0974: public void addLifecycleListener(LifecycleListener listener) {
0975:
0976: lifecycle.addLifecycleListener(listener);
0977:
0978: }
0979:
0980: /**
0981: * Get the lifecycle listeners associated with this lifecycle. If this
0982: * Lifecycle has no listeners registered, a zero-length array is returned.
0983: */
0984: public LifecycleListener[] findLifecycleListeners() {
0985:
0986: return lifecycle.findLifecycleListeners();
0987:
0988: }
0989:
0990: /**
0991: * Remove a lifecycle event listener from this component.
0992: *
0993: * @param listener The listener to remove
0994: */
0995: public void removeLifecycleListener(LifecycleListener listener) {
0996:
0997: lifecycle.removeLifecycleListener(listener);
0998:
0999: }
1000:
1001: /**
1002: * Prepare for the beginning of active use of the public methods of this
1003: * component. This method should be called after <code>configure()</code>,
1004: * and before any of the public methods of the component are utilized.
1005: *
1006: * @exception LifecycleException if this component detects a fatal error
1007: * that prevents this component from being used
1008: */
1009: public void start() throws LifecycleException {
1010:
1011: // Validate and update our current component state
1012: if (started)
1013: throw new LifecycleException(sm
1014: .getString("authenticator.alreadyStarted"));
1015: lifecycle.fireLifecycleEvent(START_EVENT, null);
1016: if ("org.apache.catalina.core.StandardContext".equals(context
1017: .getClass().getName())) {
1018: try {
1019: Class paramTypes[] = new Class[0];
1020: Object paramValues[] = new Object[0];
1021: Method method = context.getClass().getMethod(
1022: "getDebug", paramTypes);
1023: Integer result = (Integer) method.invoke(context,
1024: paramValues);
1025: setDebug(result.intValue());
1026: } catch (Exception e) {
1027: log("Exception getting debug value", e);
1028: }
1029: }
1030: started = true;
1031:
1032: // Look up the SingleSignOn implementation in our request processing
1033: // path, if there is one
1034: Container parent = context.getParent();
1035: while ((sso == null) && (parent != null)) {
1036: if (!(parent instanceof Pipeline)) {
1037: parent = parent.getParent();
1038: continue;
1039: }
1040: Valve valves[] = ((Pipeline) parent).getValves();
1041: for (int i = 0; i < valves.length; i++) {
1042: if (valves[i] instanceof SingleSignOn) {
1043: sso = (SingleSignOn) valves[i];
1044: break;
1045: }
1046: }
1047: if (sso == null)
1048: parent = parent.getParent();
1049: }
1050: if (debug >= 1) {
1051: if (sso != null)
1052: log("Found SingleSignOn Valve at " + sso);
1053: else
1054: log("No SingleSignOn Valve is present");
1055: }
1056:
1057: }
1058:
1059: /**
1060: * Gracefully terminate the active use of the public methods of this
1061: * component. This method should be the last one called on a given
1062: * instance of this component.
1063: *
1064: * @exception LifecycleException if this component detects a fatal error
1065: * that needs to be reported
1066: */
1067: public void stop() throws LifecycleException {
1068:
1069: // Validate and update our current component state
1070: if (!started)
1071: throw new LifecycleException(sm
1072: .getString("authenticator.notStarted"));
1073: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1074: started = false;
1075:
1076: sso = null;
1077:
1078: }
1079:
1080: }
|