/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

// $Id: JBossAuthenticationHandler.java,v 1.6.6.3 2005/03/03 21:01:23 tdiesler Exp $

package org.jboss.net.axis.server;

import org.jboss.axis.AxisFault;
import org.jboss.axis.MessageContext;
import org.jboss.axis.handlers.BasicHandler;
import org.jboss.security.NobodyPrincipal;
import org.jboss.security.SecurityAssociation;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SubjectSecurityManager;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import java.security.Principal;

/**
 * <p>
 * AuthenticationHandler that interacts with a given JBoss autentication
 * manager via default simple principals and passchars from the 
 * HTTP Basic Authentication. 
 * </p>
 * <p> 
 * It is derived from org.jboss.axis.handlers.SimpleAuthenticationHandler.
 * Note that this is somehow redundant to the WebContainer security, but we want
 * to be able to install different authentication schemes for different 
 * web services behind a single entry-point.
 * </p>
 * <p>
 * Per default, unauthenticated calls will be routed by NOT asking the
 * jboss auth manager for validation (most domains still would refuse NobodyPrincipal, 
 * no matter how configured) and building a "null" security association.
 * </p>
 * <p>
 * By specifiying the validateUnauthenticatedCalls=true option, the handler
 * will however try to interface the securityManager with the NobodyPrincipal and
 * an empty password and ask for a proper security association. This is 
 * important when dealing with MS Clients (thanks to John Landers for pointing
 * this out) that won�t send any authentication data if not confronted with
 * an HTTP error on the first try.
 * </p> 
 * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>
 * @since 14.03.2002
 * @version $Revision: 1.6.6.3 $
 */
public class JBossAuthenticationHandler extends BasicHandler
{

   //
   // Attributes
   //

   /** whether this handler has been initialized already */
   protected boolean isInitialised;

   /** whether this handler should let through unauthenticated calls */
   protected boolean shouldValidateUnauthenticatedCalls;

   /** 
    * this is the authentication manager that is responsible for our security domain 
    * if that is null, this authenticationhandler will block any call, rather deactivate
    * the handler, then, or run against a NullSecurityManager
    */
   protected SubjectSecurityManager authMgr;

   //
   // Constructors
   //

   /** default, all options are set afterwards */
   public JBossAuthenticationHandler()
   {
   }

   //
   // Protected helpers
   //

   /** 
    * initialize this authenticationhandler lazy, after the options have been
    * set.
    */
   protected void initialise() throws AxisFault
   {
      isInitialised = true;
      authMgr = null;
      shouldValidateUnauthenticatedCalls = false;
      String securityDomain = (String)getOption(Constants.SECURITY_DOMAIN_OPTION);
      if (securityDomain != null)
      {
         try
         {
            // bind against the jboss security subsystem
            authMgr =
                    (SubjectSecurityManager)new InitialContext().lookup(securityDomain);
         }
         catch (NamingException e)
         {
            throw new AxisFault("Could not lookup associated security domain " + securityDomain,
                    e);
         }
      }
      String unauthenticatedCalls = (String)getOption(Constants.VALIDATE_UNAUTHENTICATED_CALLS_OPTION);
      if (unauthenticatedCalls != null)
      {
         try
         {
            // bind against the jboss security subsystem
            shouldValidateUnauthenticatedCalls = new Boolean(unauthenticatedCalls).booleanValue();
         }
         catch (Exception e)
         {
            throw new AxisFault("Could not set validateUnauthenticatedCalls option.", e);
         }
      }
   }

   /** 
    * creates a new principal belonging to the given username,
    * override to adapt to specific security domains.
    */
   protected Principal getPrincipal(String userName)
   {
      if (userName == null)
      {
         return NobodyPrincipal.NOBODY_PRINCIPAL;
      }
      else
      {
         return new SimplePrincipal(userName);
      }
   }

   /** validates the given principal with the given password */
   protected Subject validate(Principal userPrincipal, String passwd) throws AxisFault
   {
      // build passchars
      char[] passChars = passwd != null ? passwd.toCharArray() : null;
      // do the validation only if authenticated or validation enforced
      Subject subject = null;
      if (shouldValidateUnauthenticatedCalls || userPrincipal != NobodyPrincipal.NOBODY_PRINCIPAL)
      {
         subject = new Subject();
         // have to use pointer comparison here, but it�s a singleton, right?
         if (!authMgr.isValid(userPrincipal, passChars, subject))
         {
            throw new AxisFault("Server.Unauthenticated",
                    org.jboss.axis.utils.Messages.getMessage
                    ("cantAuth01",
                            userPrincipal.getName()),
                    null, null);
         }
      }
      return subject;
   }

   /** associates the call context with the given info */
   protected void associate(Principal userPrincipal, String passwd, Subject subject)
   {
      // pointer comparison, again        
      if (shouldValidateUnauthenticatedCalls || userPrincipal != NobodyPrincipal.NOBODY_PRINCIPAL)
      {
         SecurityAssociation.pushSubjectContext(subject, userPrincipal, passwd);
      }
      else
      {
         // Jboss security normally does not like nobody:null
         SecurityAssociation.setPrincipal(null);
         SecurityAssociation.setCredential(null);
      }
   }

   //
   // API
   //

   /**
    * Authenticate the user and password from the msgContext. Note that
    * we do not disassociate the subject here, since that would have
    * to be done by a separate handler in the response chain and we
    * currently expect Jetty or the WebContainer to do that for us
    */

   public void invoke(MessageContext msgContext) throws AxisFault
   {

      // double check does not work on multiple processors, unfortunately
      if (!isInitialised)
      {
         synchronized (this)
         {
            if (!isInitialised)
            {
               initialise();
            }
         }
      }

      if (authMgr == null)
      {
         throw new AxisFault("No security domain associated.");
      }

      // we take the id out of the        
      String userID = msgContext.getUsername();
      // convert into a principal
      Principal userPrincipal = getPrincipal(userID);
      // the password that has been provided
      String passwd = msgContext.getPassword();
      // validate the user
      Subject subject = validate(userPrincipal, passwd);
      // associate the context
      associate(userPrincipal, passwd, subject);
      // with the security subject
      msgContext.setProperty(MessageContext.AUTHUSER, subject);
   }

}