Hi, I'm new to the list though I've been developing on Tomcat and JBoss for a few years now. Presented below are two classes I'd like feedback on. Anything is appreciated but I'm hoping for feedback mostly on the ideas behind the implementation more than particular bug fixes (that can come if I'm on the right track).

Also, I've introduced this to the users list first in case I've gone astray and there's a better way to do this elsewhere.

## Boring Backstory ## (This rambles for a bit. You can skip to the juicy bit by scrolling down to # Implementation Details #)

My background is primarily struts/hibernate based development. In that time I've had a few chances to write LoginModules (Similar to custom Realm in Tomcat) for a couple of different needs.

I recently joined a team that was using the IIS isapi_redirector.dll for tomcat to do transparent authentication. This is great for the end user. However a problem the team was having is that they're still doing programmatic security for everything. Why? The IIS connector connects to Tomcat and embeds a Principal with the users Windows Domain / Username in it. The team simply took this and then manually checked the users permissions for each operation. Yes there's a time and a place for programmatic security, but if you can let the container manage security why not? (Note: This is all being done for a MS Windows Corporate Intranet which is why the use of NTLM is accepted)

It was trivial to write a JBoss Login Module for their existing security database. However, I came across a problem when getting the authentication to happen: Tomcat wasn't authenticating the users. This makes sense since I hadn't told Tomcat to do any authentication in the WEB.XML file.

When trying to decide which Authentication I should use I reviewed the source for the existing ones and realized that none of them would work. The existing authenticators all check to see if a principal is already in the request. If it is then the user has already been authenticated and the authenticator never goes to the Realm to see what permissions / roles the user has. Since the IIS connector plugs-in a Principal for the user, this effectively short-circuited the ability of the container to bind roles to the user.

My solution was to code my own TrustedPrincipalAuthorizor (extends AuthenticatorBase). I called it an authorizor since it wasn't doing any actual Authentication. This class checks for the existence of a principal in the request. It then uses that principal as the key to tell the security realm to lookup the users permissions and bind them into the container for this request. Ergo the name. The last step of the puzzle is to make 'authenticating' (ie: checking the password) optional in your realm. I coded my LoginModule to have a flag checkPassword="false".

I know that's not the whole picture but it's the gist of it to get a discussion going. What do people here think, am I following a logical path or have I completely jumped the shark?

TrustedPrincipalAuthorizor.java

This allows me to force authentication to continue on to the realms in order to populate the user's request with their roles. (IE: use the full power of container managed security).

ContextLookupAuthenticatorBase.java

This came about because: 1. JBoss 3.2.5 doesn't support the <context> element or a context.xml file from tomcat to be able to make deployment changes to a program. 2. After studying the AuthenticatorBase I determined that it didn't actually _need_ to be hosted inside of a context element. It can get the context from the request, so it can determine dynamically which context and realm apply to the request.

This was desireable since we use this function across all our intranet applications and didn't want to have to declare it again for each app. This is actually fairly redundant and I (at present) don't see why the AuthenticatorBase must be implmented to be context dependant. (I can see why it is, just not the architectural reason why it _must be_).


## Implementation Details ##

Here's the rough source for my TrustedPrincipalAuthorizor.

The java files can be downloaded from:
http://www.laj.ca/source/tomcat/TrustedPrincipalAuthorizor.java
http://www.laj.ca/source/tomcat/ContextLookupAuthenticatorBase.java

/*
* Copyright 2007 Louis A. J. Pierrson
* based on code
* Copyright 1999-2001,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ca.laj.catalina.authenticator;

import java.io.IOException;
import java.security.Principal;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Lifecycle;
import org.apache.catalina.Realm;
import org.apache.catalina.authenticator.ContextLookupAuthenticatorBase;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* This class relies on a host container to supply the users name as a Principal.
*
* The host container used during development was IIS with the Apache
* isapi_redirector2.dll for IIS. This dll redirects IIS requests from the
* specified URI's to Tomcat (or JBoss). With
* this, IIS includes the user's LAN domain/username from IIS' NTLM authentication of * the user. In this way, we now have an authenticated security credential for * the user. ie: We know who they are. What we don't know is what they can do.
* That is for another zone.
* <p>
* Due to the need to force an 'authentication' to take place in order to be
* able to use the IIS user credential with the normal tomcat/JBoss (Java
* servlet container) security model, (ie: to lookup the user's roles)
* this custom authenticator provides a
* transparent challenge of the user. This allows us to rely on normal
* container based (declarative) security.
* <p>
* We can use this trusted credential to retreive the user's authorizations
* (via either Active Directory, company database, property file, KERBEROS,
* etc...)
*
* <p>
* PRE: This class must be used in conjunction with the isapi_redirector2.dll
* or some other mechanism that results in the user's login getting embedded
* in the session in a <em>trusted</em> fashion. A base user principal is set and the call
* request.getRemoteUser(); should not be returning null at this point.
* <p>
* To have this Authorizor recognized in web.xml by tomcat
* [EMAIL PROTECTED] org.apache.catalina.startup.ContextConfig}
* needs to be updated.
*
* <p>Installation:
* <code>
*  &lt;Host ...>
*     &lt;!--
* IISAuthorizer is embedded in a context. The context requires a directory to exist * under &lt;server-instance>\iisauthorizer as well as having a web-application using
*         the context path &lt;server-instance>\deploy\iisauthorizer.war
* The crossContext flag enables the operation of the IISConnectorAuthorizer across other
*         contexts.
*     -->
*     &lt;Context path="iisauthorizer" crossContext="true">
* &lt;Valve className="ca.laj.catalina.authenticator.IISConnectorAuthorizer" />
*     &lt;/Context>
* &lt;/Host>
* </code>
*
* @author Louis A. J. Pierrson
*/
public class TrustedPrincipalAuthorizor extends ContextLookupAuthenticatorBase implements Lifecycle {

private static final Log log = LogFactory.getLog(TrustedPrincipalAuthorizor.class);

// Authentication methods for login configuration
   public static final String NTLM_METHOD = "NTLM";
   public static final String IIS_NTLM_METHOD = "IIS_NTLM";
public String removeDomainFromPrincipal = "true"; /**
    * @return the removeDomainFromPrincipal
    */
   public String getRemoveDomainFromPrincipal() {
       return removeDomainFromPrincipal;
   }
   /**
    * @param removeDomainFromPrincipal the removeDomainFromPrincipal to set
    */
public void setRemoveDomainFromPrincipal(String removeDomainFromPrincipal) { if(removeDomainFromPrincipal == null) {
           log.trace("removeDomainFromPrincipal set to default. (true)");
       }
log.trace("Set removeDomainFromPrincipal to [" + removeDomainFromPrincipal + "]"); this.removeDomainFromPrincipal = removeDomainFromPrincipal;
   }
   /**
    * Authenticate the user making this request, based on the specified
    * login configuration.  Return <code>true</code> if any specified
    * constraint has been satisfied, or <code>false</code> if we have
    * created a response challenge already.
    *
    * @param request Request we are processing
    * @param response Response we are creating
    * @param config    Login configuration describing how authentication
    *              should be performed
    *
    * @exception IOException if an input/output error occurs
* @see ContextLookupAuthenticatorBase#authenticate(org.apache.catalina.HttpRequest, org.apache.catalina.HttpResponse, LoginConfig)
    */
public boolean authenticate(org.apache.catalina.HttpRequest catRequest, org.apache.catalina.HttpResponse catResponse, LoginConfig loginConfig)
   throws IOException
   {
       log.trace("called");
HttpServletRequest request = (HttpServletRequest)catRequest.getRequest();
       ServletResponse response = catResponse.getResponse();
HttpSession session = request.getSession(); // The following calls should all work
       // Get the user principal set by the isapi_redirector2.dll
String remoteUser = request.getRemoteUser(); // should be not null and a string like this: <domain>\\<username>
       String authType = request.getAuthType(); // should be NTLM
       String remoteAddr = request.getRemoteAddr();
       String remoteHost = request.getRemoteHost();
       Integer remotePort = new Integer(request.getRemotePort());
       Principal userPrincipal = request.getUserPrincipal();
       String authorization = request.getHeader("authorization");

// If the userPrincipal isn't set then there is an error in the
       // configuration
//XXX DISCUSS: This could fail over to BASIC, FORM or some other form of
       // authentication
       if(userPrincipal == null)
       {
           log.error("Could not authenticate the user as the expected " +
                   "user credential was not found.");
           HttpServletResponse hres =
               (HttpServletResponse) catResponse.getResponse();

           hres.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
       }
// Process an IIS based NTLM login
       if(!"NTLM".equals(authType)) {
           log.info("Not NTLM authType");
           return false;
       }
// Pick the user name and password from the remoteUser & (possibly)
       // NTLM challenge
       String ntUsername = getNTUsername(remoteUser);
       String ntDomain = getNTDomain(remoteUser);
String ntPassword = null; // Use NTLM to get the password/credential data? //FIXME
       String parameterPassword = request.getParameter("password");
       if(ntPassword == null && parameterPassword != null) {
           log.warn("Using supplied password to spoof the realm.");
           ntPassword = parameterPassword;
       }
log.debug("Authorizing user [" +
               ntDomain + ":" +
               ntUsername +
               "] using supplied NTLM credentials.");
// Instead of null, it would be good to be able to actually
       // provide some mechanism of knowing how to trust this at the
       // LoginModule layer (if indeed this goes to the login module
       // layer)
       Realm realm = getContext(catRequest).getRealm();
       log.debug("Realm container: " + realm.getContainer().getName());
       log.debug("Realm info: " + realm.getInfo());
String constraints = ""; SecurityConstraint[] sc = realm.findSecurityConstraints(catRequest, getContext(catRequest));
       for(int i = 0; i < sc.length; i++) {
           constraints += sc[i] + ", ";
       }
       log.debug("Realm info: " + constraints);
String challengeUsername = ntUsername;
       if("true".equals(removeDomainFromPrincipal)) {
userPrincipal.getName(); } log.info("Challenging the realm with [" +
               challengeUsername +
               ":" +
               ntPassword +
               "] using supplied NTLM credentials.");
Principal realmPrincipal =
           realm.authenticate(challengeUsername,ntPassword);
if(realmPrincipal == null) {
           log.info("User was not authenticated in the realm.");
           HttpServletResponse hres =
               (HttpServletResponse) catResponse.getResponse();
           hres.setStatus(HttpServletResponse.SC_FORBIDDEN);
           return false;
       }

// Set the username/password on the session and set the principal in request
//            session.setNote(Constants.SESS_USERNAME_NOTE, ntUsername);
// session.setNote(Constants.SESS_PASSWORD_NOTE, ntPassword); log.info("User was authenticated. Registering in request.");
       // Register the retrieved realm information
register(catRequest, catResponse, realmPrincipal, authType, challengeUsername, ntPassword); return true;
   }
final static int NTDOMAIN = 1;
   final static int NTUSER = 0;
   public String getNTUsername(String ntRemoteUser) {
       return splitNTRemoteUser(ntRemoteUser, NTUSER);
   }
   public String getNTDomain(String ntRemoteUser) {
       return splitNTRemoteUser(ntRemoteUser, NTDOMAIN);
   }
   public String splitNTRemoteUser(String ntRemoteUser, int position) {
       if(ntRemoteUser == null)
           return null;
String[] parts = ntRemoteUser.split("\\\\"); // Reverse the order to give priority to the name
       String[] tmp = new String[parts.length];
       for(int i = 0; i < parts.length; i++)
           tmp[i] = parts[parts.length-1 -i];
       parts = tmp;
// If domain isn't supplied return null for the domain
       if(position >= parts.length)
           return null;
return parts[position]; } }









/*
* Copyright 2007 Louis A. J. Pierrson
* from original code
* Copyright 1999-2001,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package org.apache.catalina.authenticator;


import java.io.IOException;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Authenticator;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.HttpResponse;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Logger;
import org.apache.catalina.Manager;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Realm;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Session;
import org.apache.catalina.Valve;
import org.apache.catalina.ValveContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.util.DateTool;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.StringManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
* JBoss 3.2.5 doesn't properly support the context elements and anything
* it doesn't explicitly support aren't supported at all.
* 3.2.6 accepts and reads the context.xml file.
* This is a work around needed to define the authenticator in
* a &lt;context&gt;
* <p>
* Specifically, this AuthenticatorBase dynamically checks which context it
* is being called by to determine the relevant context specific information.
* (Like which SecurityRealm applies to the context)
* <p>
* This class completelely wraps the AuthenticatorBase in order to be able to * dynamically discover which Context(and thus Realm) a Request belongs to. (This
* is why the original comments for the AuthenticatorBase are included.)
*
* @author Louis A. J. Pierrson
* @see AuthenticatorBase
*
*<p>
* Basic implementation of the <b>Valve</b> interface that enforces the
* <code>&lt;security-constraint&gt;</code> elements in the web application
* deployment descriptor.  This functionality is implemented as a Valve
* so that it can be ommitted in environments that do not require these
* features.  Individual implementations of each supported authentication
* method can subclass this base class as required.
* <p>
* <b>USAGE CONSTRAINT</b>:  When this class is utilized, the Context to
* which it is attached (or a parent Container in a hierarchy) must have an
* associated Realm that can be used for authenticating users and enumerating
* the roles to which they have been assigned.
* <p>
* <b>USAGE CONSTRAINT</b>:  This Valve is only useful when processing HTTP
* requests.  Requests of any other type will simply be passed through.
*
* @author Craig R. McClanahan
* @version $Revision: 1.19 $ $Date: 2004/04/26 21:54:15 $
*/


public abstract class ContextLookupAuthenticatorBase
   extends AuthenticatorBase
   implements Authenticator, Lifecycle {
private static Log log = LogFactory.getLog(ContextLookupAuthenticatorBase.class);


// ----------------------------------------------------- Instance Variables


   /**
    * The default message digest algorithm to use if we cannot use
    * the requested one.
    */
   protected static final String DEFAULT_ALGORITHM = "MD5";


   /**
    * The number of random bytes to include when generating a
    * session identifier.
    */
   protected static final int SESSION_ID_BYTES = 16;


   /**
    * The message digest algorithm to be used when generating session
    * identifiers.  This must be an algorithm supported by the
    * <code>java.security.MessageDigest</code> class on your platform.
    */
   protected String algorithm = DEFAULT_ALGORITHM;


   /**
    * Should we cache authenticated Principals if the request is part of
    * an HTTP session?
    */
   protected boolean cache = true;


   /**
    * The Context to which this Valve is attached.
    */
   protected Context context = null;


   /**
    * The debugging detail level for this component.
    */
   protected int debug = 0;


   /**
    * Return the MessageDigest implementation to be used when
    * creating session identifiers.
    */
   protected MessageDigest digest = null;


   /**
    * A String initialization parameter used to increase the entropy of
    * the initialization of our random number generator.
    */
   protected String entropy = null;


   /**
    * Descriptive information about this implementation.
    */
   protected static final String info =
       "org.apache.catalina.authenticator.AuthenticatorBase/1.0";

   /**
    * Flag to determine if we disable proxy caching, or leave the issue
    * up to the webapp developer.
    */
   protected boolean disableProxyCaching = true;

   /**
    * The lifecycle event support for this component.
    */
   protected LifecycleSupport lifecycle = new LifecycleSupport(this);


   /**
    * A random number generator to use when generating session identifiers.
    */
   protected Random random = null;


   /**
    * The Java class name of the random number generator class to be used
    * when generating session identifiers.
    */
   protected String randomClass = "java.security.SecureRandom";


   /**
    * The string manager for this package.
    */
   protected static final StringManager sm =
       StringManager.getManager(Constants.Package);


   /**
    * The SingleSignOn implementation in our request processing chain,
    * if there is one.
    */
   protected SingleSignOn sso = null;


   /**
    * Has this component been started?
    */
   protected boolean started = false;


   /**
    * "Expires" header always set to Date(1), so generate once only
    */
   private static final String DATE_ONE =
       (new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
                             Locale.US)).format(new Date(1));


// ------------------------------------------------------------- Properties


   /**
    * Return the message digest algorithm for this Manager.
    */
   public String getAlgorithm() {

       return (this.algorithm);

   }


   /**
    * Set the message digest algorithm for this Manager.
    *
    * @param algorithm The new message digest algorithm
    */
   public void setAlgorithm(String algorithm) {

       this.algorithm = algorithm;

   }


   /**
    * Return the cache authenticated Principals flag.
    */
   public boolean getCache() {

       return (this.cache);

   }


   /**
    * Set the cache authenticated Principals flag.
    *
    * @param cache The new cache flag
    */
   public void setCache(boolean cache) {

       this.cache = cache;

   }

   /**
    * Checks if the defined <code>container</code> is a Context. If not
    * it will look up the context for the supplied request.
    *
    * @param request
    * @return
    */
   protected Context getContext(Request request) {
       if(this.context instanceof Context)
           return this.context;
       else
           return request.getContext();
   }

   /**
    * Return the Container to which this Valve is attached.
    */
   public Container getContainer() {

       return (this.context);

   }


   /**
    * Set the Container to which this Valve is attached.
    *
    * @param container The container to which we are attached
    */
   public void setContainer(Container container) {
       // Ignore behaviour of original AuthenticatorBase
       //super.setContainer(container);
       this.container = container;

       if (!(container instanceof Context)) {
           log.warn("Running using context lookup for each request.");
//            throw new IllegalArgumentException
//                (sm.getString("authenticator.notContext"));
       } else {
           this.context = (Context) container;
       }


   }


   /**
    * Return the debugging detail level for this component.
    */
   public int getDebug() {

       return (this.debug);

   }


   /**
    * Set the debugging detail level for this component.
    *
    * @param debug The new debugging detail level
    */
   public void setDebug(int debug) {

       this.debug = debug;

   }


   /**
    * Return the entropy increaser value, or compute a semi-useful value
    * if this String has not yet been set.
    */
   public String getEntropy() {

       // Calculate a semi-useful value if this has not been set
       if (this.entropy == null)
           setEntropy(this.toString());

       return (this.entropy);

   }


   /**
    * Set the entropy increaser value.
    *
    * @param entropy The new entropy increaser value
    */
   public void setEntropy(String entropy) {

       this.entropy = entropy;

   }


   /**
    * Return descriptive information about this Valve implementation.
    */
   public String getInfo() {

       return (info);

   }


   /**
    * Return the random number generator class name.
    */
   public String getRandomClass() {

       return (this.randomClass);

   }


   /**
    * Set the random number generator class name.
    *
    * @param randomClass The new random number generator class name
    */
   public void setRandomClass(String randomClass) {

       this.randomClass = randomClass;

   }

   /**
    * Return the flag that states if we add headers to disable caching by
    * proxies.
    */
   public boolean getDisableProxyCaching() {
       return disableProxyCaching;
   }

   /**
    * Set the value of the flag that states if we add headers to disable
    * caching by proxies.
    * @param nocache <code>true</code> if we add headers to disable proxy
* caching, <code>false</code> if we leave the headers alone.
    */
   public void setDisableProxyCaching(boolean nocache) {
       disableProxyCaching = nocache;
   }

// --------------------------------------------------------- Public Methods


   /**
    * Enforce the security restrictions in the web application deployment
    * descriptor of our associated Context.
    *
    * @param request Request to be processed
    * @param response Response to be processed
    * @param context The valve context used to invoke the next valve
    *  in the current processing pipeline
    *
    * @exception IOException if an input/output error occurs
    * @exception ServletException if thrown by a processing element
    */
   public void invoke(Request request, Response response,
                      ValveContext context)
       throws IOException, ServletException {

       // If this is not an HTTP request, do nothing
       if (!(request instanceof HttpRequest) ||
           !(response instanceof HttpResponse)) {
           context.invokeNext(request, response);
           return;
       }
       if (!(request.getRequest() instanceof HttpServletRequest) ||
           !(response.getResponse() instanceof HttpServletResponse)) {
           context.invokeNext(request, response);
           return;
       }
       HttpRequest hrequest = (HttpRequest) request;
       HttpResponse hresponse = (HttpResponse) response;
       if (log.isDebugEnabled())
           log.debug("Security checking request " +
((HttpServletRequest) request.getRequest()).getMethod() + " " + ((HttpServletRequest) request.getRequest()).getRequestURI());
       LoginConfig config = getContext(request).getLoginConfig();
if(config == null || !"IIS-NTLM".equals(config.getAuthMethod())) {
           context.invokeNext(request, response);
           return;
       }

       // Have we got a cached authenticated Principal to record?
       if (cache) {
           Principal principal =
((HttpServletRequest) request.getRequest()).getUserPrincipal();
           if (principal == null) {
               Session session = getSession(hrequest);
               if (session != null) {
                   principal = session.getPrincipal();
                   if (principal != null) {
                       if (log.isDebugEnabled())
                           log.debug("We have cached auth type " +
                               session.getAuthType() +
                               " for principal " +
                               session.getPrincipal());
                       hrequest.setAuthType(session.getAuthType());
                       hrequest.setUserPrincipal(principal);
                   }
               }
           }
       }

       // Special handling for form-based logins to deal with the case
       // where the login form (and therefore the "j_security_check" URI
       // to which it submits) might be outside the secured area
       String contextPath = getContext(request).getPath();
       String requestURI = hrequest.getDecodedRequestURI();
       if (requestURI.startsWith(contextPath) &&
           requestURI.endsWith(Constants.FORM_ACTION)) {
           if (!authenticate(hrequest, hresponse, config)) {
               if (log.isDebugEnabled())
log.debug(" Failed authenticate() test ??" + requestURI );
               return;
           }
       }

       Realm realm = getContext(request).getRealm();
       // Is this request URI subject to a security constraint?
       SecurityConstraint [] constraints
           = realm.findSecurityConstraints(hrequest, getContext(request));
if ((constraints == null) /* &&
           (!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
           if (log.isDebugEnabled())
               log.debug(" Not subject to any constraint");
           context.invokeNext(request, response);
           return;
       }

// Make sure that constrained resources are not cached by web proxies
       // or browsers as caching can provide a security hole
HttpServletRequest hsrequest = (HttpServletRequest)hrequest.getRequest();
       if (disableProxyCaching &&
           // FIXME: Disabled for Mozilla FORM support over SSL
           // (improper caching issue)
           //!hsrequest.isSecure() &&
           !"POST".equalsIgnoreCase(hsrequest.getMethod())) {
           HttpServletResponse sresponse =
               (HttpServletResponse) response.getResponse();
           sresponse.setHeader("Pragma", "No-cache");
           sresponse.setHeader("Cache-Control", "no-cache");
           sresponse.setHeader("Expires", DATE_ONE);
       }

       int i;
       // Enforce any user data constraint for this security constraint
       if (log.isDebugEnabled()) {
           log.debug(" Calling hasUserDataPermission()");
       }
       if (!realm.hasUserDataPermission(hrequest, hresponse,
                                        constraints)) {
           if (log.isDebugEnabled()) {
               log.debug(" Failed hasUserDataPermission() test");
           }
           /*
            * ASSERT: Authenticator already set the appropriate
            * HTTP status code, so we do not have to do anything special
            */
           return;
       }
for(i=0; i < constraints.length; i++) {
           // Authenticate based upon the specified login configuration
           if (constraints[i].getAuthConstraint()) {
               if (log.isDebugEnabled()) {
                   log.debug(" Calling authenticate()");
               }
               if (!authenticate(hrequest, hresponse, config)) {
                   if (log.isDebugEnabled()) {
                       log.debug(" Failed authenticate() test");
                   }
                   /*
                    * ASSERT: Authenticator already set the appropriate
                    * HTTP status code, so we do not have to do anything
                    * special
                    */
                   return;
               } else {
                   break;
               }
           }
       }
       if (log.isDebugEnabled()) {
           log.debug(" Calling accessControl()");
       }
       if (!realm.hasResourcePermission(hrequest, hresponse,
                                        constraints,
                                        getContext(request))) {
           if (log.isDebugEnabled()) {
               log.debug(" Failed accessControl() test");
           }
           /*
            * ASSERT: AccessControl method has already set the
            * appropriate HTTP status code, so we do not have to do
            * anything special
            */
           return;
       }
// Any and all specified constraints have been satisfied
       if (log.isDebugEnabled()) {
           log.debug(" Successfully passed all security constraints");
       }
       context.invokeNext(request, response);

   }


// ------------------------------------------------------ Protected Methods




   /**
    * Associate the specified single sign on identifier with the
    * specified Session.
    *
    * @param ssoId Single sign on identifier
    * @param session Session to be associated
    */
   protected void associate(String ssoId, Session session) {

       if (sso == null)
           return;
       sso.associate(ssoId, session);

   }


   /**
    * Authenticate the user making this request, based on the specified
    * login configuration.  Return <code>true</code> if any specified
    * constraint has been satisfied, or <code>false</code> if we have
    * created a response challenge already.
    *
    * @param request Request we are processing
    * @param response Response we are creating
    * @param config    Login configuration describing how authentication
    *              should be performed
    *
    * @exception IOException if an input/output error occurs
    */
   protected abstract boolean authenticate(HttpRequest request,
                                           HttpResponse response,
                                           LoginConfig config)
       throws IOException;


   /**
    * Generate and return a new session identifier for the cookie that
    * identifies an SSO principal.
    */
   protected synchronized String generateSessionId() {

       // Generate a byte array containing a session identifier
       byte bytes[] = new byte[SESSION_ID_BYTES];
       getRandom().nextBytes(bytes);
       bytes = getDigest().digest(bytes);

       // Render the result as a String of hexadecimal digits
       StringBuffer result = new StringBuffer();
       for (int i = 0; i < bytes.length; i++) {
           byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
           byte b2 = (byte) (bytes[i] & 0x0f);
           if (b1 < 10)
               result.append((char) ('0' + b1));
           else
               result.append((char) ('A' + (b1 - 10)));
           if (b2 < 10)
               result.append((char) ('0' + b2));
           else
               result.append((char) ('A' + (b2 - 10)));
       }
       return (result.toString());

   }


   /**
    * Return the MessageDigest object to be used for calculating
    * session identifiers.  If none has been created yet, initialize
    * one the first time this method is called.
    */
   protected synchronized MessageDigest getDigest() {

       if (this.digest == null) {
           try {
               this.digest = MessageDigest.getInstance(algorithm);
           } catch (NoSuchAlgorithmException e) {
               try {
this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
               } catch (NoSuchAlgorithmException f) {
                   this.digest = null;
               }
           }
       }

       return (this.digest);

   }


   /**
    * Return the random number generator instance we should use for
    * generating session identifiers.  If there is no such generator
    * currently defined, construct and seed a new one.
    */
   protected synchronized Random getRandom() {

       if (this.random == null) {
           try {
               Class clazz = Class.forName(randomClass);
               this.random = (Random) clazz.newInstance();
               long seed = System.currentTimeMillis();
               char entropy[] = getEntropy().toCharArray();
               for (int i = 0; i < entropy.length; i++) {
                   long update = ((byte) entropy[i]) << ((i % 8) * 8);
                   seed ^= update;
               }
               this.random.setSeed(seed);
           } catch (Exception e) {
               this.random = new java.util.Random();
           }
       }

       return (this.random);

   }


   /**
    * Return the internal Session that is associated with this HttpRequest,
    * or <code>null</code> if there is no such Session.
    *
    * @param request The HttpRequest we are processing
    */
   protected Session getSession(HttpRequest request) {

       return (getSession(request, false));

   }


   /**
    * Return the internal Session that is associated with this HttpRequest,
    * possibly creating a new one if necessary, or <code>null</code> if
    * there is no such session and we did not create one.
    *
    * @param request The HttpRequest we are processing
    * @param create Should we create a session if needed?
    */
   protected Session getSession(HttpRequest request, boolean create) {

       HttpServletRequest hreq =
           (HttpServletRequest) request.getRequest();
       HttpSession hses = hreq.getSession(create);
       if (hses == null)
           return (null);
       Manager manager = getContext(request).getManager();
       if (manager == null)
           return (null);
       else {
           try {
               return (manager.findSession(hses.getId()));
           } catch (IOException e) {
               return (null);
           }
       }

   }


   /**
    * Log a message on the Logger associated with our Container (if any).
    *
    * @param message Message to be logged
    */
   protected void log(String message) {

       Logger logger = context.getLogger();
       if (logger != null)
           logger.log("Authenticator[" + context.getPath() + "]: " +
                      message);
       else
           System.out.println("Authenticator[" + context.getPath() +
                              "]: " + message);

   }


   /**
    * Log a message on the Logger associated with our Container (if any).
    *
    * @param message Message to be logged
    * @param throwable Associated exception
    */
   protected void log(String message, Throwable throwable) {

       Logger logger = null;
       if(context != null)
           logger = context.getLogger();
if (logger != null)
           logger.log("Authenticator[" + context.getPath() + "]: " +
                      message, throwable);
       else {
           System.out.println("Authenticator[" + context.getPath() +
                              "]: " + message);
           throwable.printStackTrace(System.out);
       }

   }


   /**
    * Attempts reauthentication to the <code>Realm</code> using
    * the credentials included in argument <code>entry</code>.
    *
    * @param ssoId identifier of SingleSignOn session with which the
    *              caller is associated
    * @param request   the request that needs to be authenticated
    */
   protected boolean reauthenticateFromSSO
       (String ssoId, HttpRequest request) {

       if (sso == null || ssoId == null)
           return false;

       boolean reauthenticated = false;

       Container parent = getContainer();
       if (parent != null) {
           Realm realm = parent.getRealm();
           if (realm != null) {
               reauthenticated = sso.reauthenticate(ssoId, realm, request);
           }
       }

       if (reauthenticated) {
           associate(ssoId, getSession(request, true));

           if (log.isDebugEnabled()) {
               HttpServletRequest hreq =
                   (HttpServletRequest) request.getRequest();
               log.debug(" Reauthenticated cached principal '" +
                         hreq.getUserPrincipal().getName() +
                         "' with auth type '" +  hreq.getAuthType() + "'");
           }
       }

       return reauthenticated;
   }


   /**
    * Register an authenticated Principal and authentication type in our
    * request, in the current session (if there is one), and with our
    * SingleSignOn valve, if there is one.  Set the appropriate cookie
    * to be returned.
    *
    * @param request The servlet request we are processing
    * @param response The servlet response we are generating
    * @param principal The authenticated Principal to be registered
    * @param authType The authentication type to be registered
    * @param username Username used to authenticate (if any)
    * @param password Password used to authenticate (if any)
    */
   protected void register(HttpRequest request, HttpResponse response,
                           Principal principal, String authType,
                           String username, String password) {

       if (log.isDebugEnabled())
log.debug("Authenticated '" + principal.getName() + "' with type '"
               + authType + "'");

       // Cache the authentication information in our request
       request.setAuthType(authType);
       request.setUserPrincipal(principal);

       Session session = getSession(request, false);
       // Cache the authentication information in our session, if any
       if (cache) {
           if (session != null) {
               session.setAuthType(authType);
               session.setPrincipal(principal);
               if (username != null)
                   session.setNote(Constants.SESS_USERNAME_NOTE, username);
               else
                   session.removeNote(Constants.SESS_USERNAME_NOTE);
               if (password != null)
                   session.setNote(Constants.SESS_PASSWORD_NOTE, password);
               else
                   session.removeNote(Constants.SESS_PASSWORD_NOTE);
           }
       }

       // Construct a cookie to be returned to the client
       if (sso == null)
           return;

       // Only create a new SSO entry if the SSO did not already set a note
       // for an existing entry (as it would do with subsequent requests
       // for DIGEST and SSL authenticated contexts)
       String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
       if (ssoId == null) {
           // Construct a cookie to be returned to the client
           HttpServletResponse hres =
               (HttpServletResponse) response.getResponse();
           ssoId = generateSessionId();
Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, ssoId);
           cookie.setMaxAge(-1);
           cookie.setPath("/");
           hres.addCookie(cookie);

           // Register this principal with our SSO valve
           sso.register(ssoId, principal, authType, username, password);
           request.setNote(Constants.REQ_SSOID_NOTE, ssoId);

       } else {
           // Update the SSO session with the latest authentication data
           sso.update(ssoId, principal, authType, username, password);
       }

       // Fix for Bug 10040
       // Always associate a session with a new SSO reqistration.
       // SSO entries are only removed from the SSO registry map when
       // associated sessions are destroyed; if a new SSO entry is created
// above for this request and the user never revisits the context, the
       // SSO entry will never be cleared if we don't associate the session
       if (session == null)
           session = getSession(request, true);
       sso.associate(ssoId, session);

   }


// ------------------------------------------------------ Lifecycle Methods


   /**
    * Add a lifecycle event listener to this component.
    *
    * @param listener The listener to add
    */
   public void addLifecycleListener(LifecycleListener listener) {

       lifecycle.addLifecycleListener(listener);

   }


   /**
    * Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
    */
   public LifecycleListener[] findLifecycleListeners() {

       return lifecycle.findLifecycleListeners();

   }


   /**
    * Remove a lifecycle event listener from this component.
    *
    * @param listener The listener to remove
    */
   public void removeLifecycleListener(LifecycleListener listener) {

       lifecycle.removeLifecycleListener(listener);

   }


   /**
    * Prepare for the beginning of active use of the public methods of this
* component. This method should be called after <code>configure()</code>,
    * and before any of the public methods of the component are utilized.
    *
    * @exception LifecycleException if this component detects a fatal error
    *  that prevents this component from being used
    */
   public void start() throws LifecycleException {

       // Validate and update our current component state
       if (started)
           throw new LifecycleException
               (sm.getString("authenticator.alreadyStarted"));
       lifecycle.fireLifecycleEvent(START_EVENT, null);
       if(context != null)
       if ("org.apache.catalina.core.StandardContext".equals
           (context.getClass().getName())) {
           try {
               // XXX What is this ???
               Class paramTypes[] = new Class[0];
               Object paramValues[] = new Object[0];
               Method method =
                   container.getClass().getMethod("getDebug", paramTypes);
Integer result = (Integer) method.invoke(context, paramValues);
               setDebug(result.intValue());
           } catch (Exception e) {
               log.error("Exception getting debug value", e);
           }
       }
       started = true;

       // Look up the SingleSignOn implementation in our request processing
       // path, if there is one
       Container parent = container;
if(context != null)
           parent = context.getParent();
while ((sso == null) && (parent != null)) {
           if (!(parent instanceof Pipeline)) {
               parent = parent.getParent();
               continue;
           }
           Valve valves[] = ((Pipeline) parent).getValves();
           for (int i = 0; i < valves.length; i++) {
               if (valves[i] instanceof SingleSignOn) {
                   sso = (SingleSignOn) valves[i];
                   break;
               }
           }
           if (sso == null)
               parent = parent.getParent();
       }
       if (log.isDebugEnabled()) {
           if (sso != null)
               log.debug("Found SingleSignOn Valve at " + sso);
           else
               log.debug("No SingleSignOn Valve is present");
       }

   }


   /**
    * Gracefully terminate the active use of the public methods of this
    * component.  This method should be the last one called on a given
    * instance of this component.
    *
    * @exception LifecycleException if this component detects a fatal error
    *  that needs to be reported
    */
   public void stop() throws LifecycleException {

       // Validate and update our current component state
       if (!started)
           throw new LifecycleException
               (sm.getString("authenticator.notStarted"));
       lifecycle.fireLifecycleEvent(STOP_EVENT, null);
       started = false;

       sso = null;

   }


}


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to