/*
 * JBoss, the OpenSource WebOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.web.tomcat.security;

import java.io.IOException;
import java.security.Principal;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Manager;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
import org.apache.catalina.Session;
import org.apache.catalina.ValveContext;
import org.apache.catalina.Wrapper;
import org.apache.catalina.valves.ValveBase;
import org.jboss.logging.Logger;
import org.jboss.metadata.WebMetaData;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SubjectSecurityManager;
import org.jboss.security.plugins.JaasSecurityManagerServiceMBean;

/**
 * A Valve that sets/clears the SecurityAssociation information associated with
 * the request thread for identity propagation.
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.1.1.1.2.8 $
 */
public class SecurityAssociationValve extends ValveBase
{
   private static Logger log = Logger.getLogger(SecurityAssociationValve.class);
   public static ThreadLocal userPrincipal = new ThreadLocal();

   /**
    * The web app metadata
    */
   private WebMetaData metaData;
   /**
    * The name in the session under which the Subject is stored
    */
   private String subjectAttributeName = null;
   /**
    * The service used to flush authentication cache on session invalidation.
    */
   private JaasSecurityManagerServiceMBean secMgrService;
   private boolean trace;

   public SecurityAssociationValve(WebMetaData metaData,
      JaasSecurityManagerServiceMBean secMgrService)
   {
      this.metaData = metaData;
      this.secMgrService = secMgrService;
      this.trace = log.isTraceEnabled();
   }

   /**
    * The name of the request attribute under with the authenticated JAAS
    * Subject is stored on successful authentication. If null or empty then
    * the Subject will not be stored.
    */
   public void setSubjectAttributeName(String subjectAttributeName)
   {
      this.subjectAttributeName = subjectAttributeName;
      if (subjectAttributeName != null && subjectAttributeName.length() == 0)
         this.subjectAttributeName = null;
   }

   public void invoke(Request request, Response response, ValveContext context)
      throws IOException, ServletException
   {
      Session session = null;
      // Get the request caller which could be set due to SSO
      HttpServletRequest httpRequest = (HttpServletRequest) request.getRequest();
      Principal caller = httpRequest.getUserPrincipal();
      try
      {
         String runAsRole = null;
         try
         {
            Wrapper servlet = request.getWrapper();
            if (servlet != null)
            {
               runAsRole = servlet.getRunAs();
               if (trace)
                  log.trace(servlet.getName() + ", runAs: " + runAsRole);
               if (runAsRole != null)
               {
                  SimplePrincipal runAs = new SimplePrincipal(runAsRole);
                  SecurityAssociationActions.pushRunAsRole(runAs);
               }
            }
            userPrincipal.set(caller);

            JBossGenericPrincipal principal = null;
            if (caller == null || (caller instanceof JBossGenericPrincipal) == false)
            {
               // Look to the session for the active caller security context
               HttpSession hsession = httpRequest.getSession(false);
               Manager manager = container.getManager();
               if (manager != null && hsession != null)
               {
                  try
                  {
                     session = manager.findSession(hsession.getId());
                  }
                  catch (IOException ignore)
                  {
                  }
               }
               if (session != null)
               {
                  principal =
                     (JBossGenericPrincipal) session.getPrincipal();
               }
            }
            else
            {
               // Use the request principal as the caller identity
               principal = (JBossGenericPrincipal) caller;
            }

            // If there is a caller use this as the identity to propagate
            if (principal != null)
            {
               if (trace)
                  log.trace("Restoring principal info from cache");
               SecurityAssociationActions.setPrincipalInfo(principal.getPrincipal(),
                  principal.getCredentials(), principal.getSubject());
            }
            // Put the authenticated subject in the session if requested
            if (subjectAttributeName != null)
            {
               javax.naming.Context securityCtx = getSecurityContext();
               if (securityCtx != null)
               {
                  // Get the JBoss security manager from the ENC context
                  SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
                  Subject subject = securityMgr.getActiveSubject();
                  httpRequest.setAttribute(subjectAttributeName, subject);
               }
            }

         }
         catch (Throwable e)
         {
            log.debug("Failed to determine servlet", e);
         }
         // Perform the request
         context.invokeNext(request, response);
         if (runAsRole != null)
            SecurityAssociationActions.popRunAsRole();

         /* If the security domain cache is to be kept in synch with the
         session then flush the cache if the session has been invalidated.
         */
         if (secMgrService != null &&
            session != null && session.isValid() == false &&
            metaData.isFlushOnSessionInvalidation() == true)
         {
            Principal user = httpRequest.getUserPrincipal();
            if (user != null)
            {
               String securityDomain = metaData.getSecurityDomain();
               if (trace)
               {
                  log.trace("Session is invalid, security domain: " + securityDomain
                     + ", user=" + user);
               }
               try
               {
                  secMgrService.flushAuthenticationCache(securityDomain, user);
               }
               catch (Exception e)
               {
                  log.debug("Failed to flush auth cache", e);
               }
            }
         }

      }
      finally
      {
         SecurityAssociationActions.clear();
         userPrincipal.set(null);
      }
   }

   private javax.naming.Context getSecurityContext()
   {
      javax.naming.Context securityCtx = null;
      // Get the JBoss security manager from the ENC context
      try
      {
         InitialContext iniCtx = new InitialContext();
         securityCtx = (javax.naming.Context) iniCtx.lookup("java:comp/env/security");
      }
      catch (NamingException e)
      {
         // Apparently there is no security context?
      }
      return securityCtx;
   }
}
