/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.test.security.test;

import java.rmi.RemoteException;
import java.rmi.AccessException;
import java.rmi.ServerException;
import java.util.HashSet;
import javax.ejb.Handle;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;

import org.jboss.security.auth.login.XMLLoginConfigImpl;
import org.jboss.security.plugins.JaasSecurityManagerServiceMBean;
import org.jboss.security.SimplePrincipal;
import org.jboss.test.JBossTestCase;
import org.jboss.test.JBossTestSetup;
import org.jboss.test.security.interfaces.CalledSession;
import org.jboss.test.security.interfaces.CalledSessionHome;
import org.jboss.test.security.interfaces.StatefulSession;
import org.jboss.test.security.interfaces.StatefulSessionHome;
import org.jboss.test.security.interfaces.StatelessSession;
import org.jboss.test.security.interfaces.StatelessSessionHome;
import org.jboss.test.security.interfaces.SecurityContext;
import org.jboss.test.security.interfaces.SecurityContextHome;
import org.jboss.test.security.ejb.jbas1852.SessionFacade;
import org.jboss.test.security.ejb.jbas1852.SessionFacadeHome;
import org.jboss.test.util.AppCallbackHandler;
import org.apache.log4j.Logger;

import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;


/** Test of EJB spec conformace using the security-spec.jar
 deployment unit. These test the basic role based access model.
 
 @author Scott.Stark@jboss.org
 @version $Revision: 1.14.2.18 $
 */
public class EJBSpecUnitTestCase
   extends JBossTestCase
{
   static String username = "scott";
   static char[] password = "echoman".toCharArray();
   static String QUEUE_FACTORY = "ConnectionFactory";
   
   LoginContext lc;
   boolean loggedIn;

   public EJBSpecUnitTestCase(String name)
   {
      super(name);
   }

   /** Validate that the users have the expected logins and roles.
    * 
    * @throws Exception
    */ 
   public void testSecurityDomain() throws Exception
   {
      log.info("+++ testSecurityDomain, domain=spec-test");
      MBeanServerConnection conn = (MBeanServerConnection) getServer();
      ObjectName secMgrName = new ObjectName("jboss.security:service=JaasSecurityManager");
      JaasSecurityManagerServiceMBean secMgr = (JaasSecurityManagerServiceMBean)
         MBeanServerInvocationHandler.newProxyInstance(conn, secMgrName,
         JaasSecurityManagerServiceMBean.class, false);

      // Test the spec-test security domain
      String domain = "spec-test";
      SimplePrincipal user = new SimplePrincipal("scott");
      boolean isValid = secMgr.isValid(domain, user, password);
      assertTrue("scott password is echoman", isValid);
      HashSet testRole = new HashSet();
      testRole.add(new SimplePrincipal("Echo"));
      boolean hasRole = secMgr.doesUserHaveRole(domain, user, password, testRole);
      assertTrue("scott has Echo role", hasRole);
      testRole.clear();
      testRole.add(new SimplePrincipal("EchoLocal"));
      hasRole = secMgr.doesUserHaveRole(domain, user, password, testRole);
      assertTrue("scott has EchoLocal role", hasRole);
      testRole.clear();
      testRole.add(new SimplePrincipal("ProjectUser"));
      hasRole = secMgr.doesUserHaveRole(domain, user, password, testRole);
      assertTrue("scott has ProjectUser role", hasRole);

      isValid = secMgr.isValid(domain, user, "badpass".toCharArray());
      assertTrue("badpass is an invalid password for scott", isValid == false);

      // Test the spec-test-domain security domain
      log.info("+++ testSecurityDomain, domain=spec-test-domain");
      domain = "spec-test-domain";
      isValid = secMgr.isValid(domain, user, password);
      assertTrue("scott password is echoman", isValid);
      hasRole = secMgr.doesUserHaveRole(domain, user, password, testRole);
      assertTrue("scott has Echo role", hasRole);
      testRole.clear();
      testRole.add(new SimplePrincipal("EchoLocal"));
      hasRole = secMgr.doesUserHaveRole(domain, user, password, testRole);
      assertTrue("scott has EchoLocal role", hasRole);
      testRole.clear();
      testRole.add(new SimplePrincipal("ProjectUser"));
      hasRole = secMgr.doesUserHaveRole(domain, user, password, testRole);
      assertTrue("scott has ProjectUser role", hasRole);      

      isValid = secMgr.isValid(domain, user, "badpass".toCharArray());
      assertTrue("badpass is an invalid password for scott", isValid == false);
   }

   /** Test the use of getCallerPrincipal from within the ejbCreate
    * in a stateful session bean
    */
   public void testStatefulCreateCaller() throws Exception
   {
      log.debug("+++ testStatefulCreateCaller");
      login();
      InitialContext jndiContext = new InitialContext();
      Object obj = jndiContext.lookup("spec.StatefulSession");
      obj = PortableRemoteObject.narrow(obj, StatefulSessionHome.class);
      StatefulSessionHome home = (StatefulSessionHome) obj;
      log.debug("Found StatefulSessionHome");
      // The create should be allowed to call getCallerPrincipal
      StatefulSession bean = home.create("testStatefulCreateCaller");
      // Need to invoke a method to ensure an ejbCreate call
      bean.echo("testStatefulCreateCaller");
      log.debug("Bean.echo(), ok");

      logout();
   }

   /** Test that:
    1. SecureBean returns a non-null principal when getCallerPrincipal
    is called with a security context and that this is propagated
    to its Entity bean ref.
    
    2. UnsecureBean throws an IllegalStateException when getCallerPrincipal
    is called without a security context.
    */
   public void testGetCallerPrincipal() throws Exception
   {
      logout();
      log.debug("+++ testGetCallerPrincipal()");
      Object obj = getInitialContext().lookup("spec.UnsecureStatelessSession2");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found Unsecure StatelessSessionHome");
      StatelessSession bean = home.create();
      log.debug("Created spec.UnsecureStatelessSession2");
      
      try
      {
         // This should fail because echo calls getCallerPrincipal()
         bean.echo("Hello from nobody?");
         fail("Was able to call StatelessSession.echo");
      }
      catch(RemoteException e)
      {
         log.debug("echo failed as expected");
      }
      bean.remove();
      
      login();
      obj = getInitialContext().lookup("spec.StatelessSession2");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      home = (StatelessSessionHome) obj;
      log.debug("Found spec.StatelessSession2");
      bean = home.create();
      log.debug("Created spec.StatelessSession2");
      // Test that the Entity bean sees username as its principal
      String echo = bean.echo(username);
      log.debug("bean.echo(username) = "+echo);
      assertTrue("username == echo", echo.equals(username));
      bean.remove();
   }

   /**
    * Test that a call interacting with different security domains does not
    * change the 
    * @throws Exception
    */ 
   public void testDomainInteraction() throws Exception
   {
      logout();
      login("testDomainInteraction", "testDomainInteraction".toCharArray());
      log.debug("+++ testDomainInteraction()");
      Object obj = getInitialContext().lookup("spec.UserInRoleContextSession");
      obj = PortableRemoteObject.narrow(obj, SecurityContextHome.class);
      SecurityContextHome home = (SecurityContextHome) obj;
      log.debug("Found UserInRoleContextSession");
      SecurityContext bean = home.create();
      log.debug("Created spec.UserInRoleContextSession");
      HashSet roles = new HashSet();
      roles.add("Role1");
      roles.add("Role2");
      bean.testDomainInteraction(roles);
      bean.remove();      
   }

   /** Test that the calling principal is propagated across bean calls.
    */
   public void testPrincipalPropagation() throws Exception
   {
      log.debug("+++ testPrincipalPropagation");
      logout();
      login();
      Object obj = getInitialContext().lookup("spec.UnsecureStatelessSession2");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found Unsecure StatelessSessionHome");
      StatelessSession bean = home.create();
      log.debug("Created spec.UnsecureStatelessSession2");
      log.debug("Bean.forward('testPrincipalPropagation') -> "+bean.forward("testPrincipalPropagation"));
      bean.remove();
   }
   
   /** Test that the echo method is accessible by an Echo
    role. Since the noop() method of the StatelessSession
    bean was not assigned any permissions it should be unchecked.
    */
   public void testMethodAccess() throws Exception
   {
      log.debug("+++ testMethodAccess");
      login();
      Object obj = getInitialContext().lookup("spec.StatelessSession");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found StatelessSessionHome");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSession");
      log.debug("Bean.echo('Hello') -> "+bean.echo("Hello"));
      
      try
      {
         // This should not be allowed
         bean.noop();
         fail("Was able to call StatelessSession.noop");
      }
      catch(RemoteException e)
      {
         log.debug("StatelessSession.noop failed as expected");
      }
      bean.remove();
   }

   /** Test that the echo method is accessible by an Echo
    role. Since the excluded() method of the StatelessSession
    bean has been placed into the excluded set it should not
    accessible by any user. This uses the security domain of the
    JaasSecurityDomain service to test its use as an authentication mgr.
    */
   public void testDomainMethodAccess() throws Exception
   {
      log.debug("+++ testDomainMethodAccess");
      login();
      Object obj = getInitialContext().lookup("spec.StatelessSessionInDomain");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found StatelessSessionInDomain home");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSessionInDomain");
      log.debug("Bean.echo('testDomainMethodAccess') -> "+bean.echo("testDomainMethodAccess"));

      try
      {
         // This should not be allowed
         bean.excluded();
         fail("Was able to call StatelessSession.excluded");
      }
      catch(RemoteException e)
      {
         log.debug("StatelessSession.excluded failed as expected");
      }
      bean.remove();
   }

   /** Test that the permissions assigned to the stateless session bean:
    with ejb-name=org/jboss/test/security/ejb/StatelessSession_test
    are read correctly.
    */
   public void testMethodAccess2() throws Exception
   {
      log.debug("+++ testMethodAccess2");
      login();
      InitialContext jndiContext = new InitialContext();
      Object obj = jndiContext.lookup("spec.StatelessSession_test");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found StatelessSessionHome");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSession_test");
      log.debug("Bean.echo('testMethodAccess2') -> "+bean.echo("testMethodAccess2"));
      bean.remove();
   }

   /** Test a user with Echo and EchoLocal roles can access the CalleeBean
    through its local interface by calling the CallerBean and that a user
    with only a EchoLocal cannot call the CallerBean.
    */
   public void testLocalMethodAccess() throws Exception
   {
      log.debug("+++ testLocalMethodAccess");
      login();
      InitialContext jndiContext = new InitialContext();
      Object obj = jndiContext.lookup("spec.CallerBean");
      obj = PortableRemoteObject.narrow(obj, CalledSessionHome.class);
      CalledSessionHome home = (CalledSessionHome) obj;
      log.debug("Found spec.CallerBean Home");
      CalledSession bean = home.create();
      log.debug("Created spec.CallerBean");
      log.debug("Bean.invokeEcho('testLocalMethodAccess') -> "+bean.invokeEcho("testLocalMethodAccess"));
      bean.remove();
   }

   /** Test access to a bean with a mix of remote interface permissions and
    * unchecked permissions with the unchecked permissions declared first.
    * @throws Exception
    */ 
   public void testUncheckedRemote() throws Exception
   {
      log.debug("+++ testUncheckedRemote");
      login();
      Object obj = getInitialContext().lookup("spec.UncheckedSessionRemoteLast");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found UncheckedSessionRemoteLast");
      StatelessSession bean = home.create();
      log.debug("Created spec.UncheckedSessionRemoteLast");
      log.debug("Bean.echo('testUncheckedRemote') -> "+bean.echo("testUncheckedRemote"));
      try
      {
         bean.excluded();
         fail("Was able to call UncheckedSessionRemoteLast.excluded");
      }
      catch(RemoteException e)
      {
         log.debug("UncheckedSessionRemoteLast.excluded failed as expected");         
      }
      bean.remove();
      logout();
   }

   /** Test access to a bean with a mix of remote interface permissions and
    * unchecked permissions with the unchecked permissions declared last.
    * @throws Exception
    */ 
   public void testRemoteUnchecked() throws Exception
   {
      log.debug("+++ testRemoteUnchecked");
      login();
      Object obj = getInitialContext().lookup("spec.UncheckedSessionRemoteFirst");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found UncheckedSessionRemoteFirst");
      StatelessSession bean = home.create();
      log.debug("Created spec.UncheckedSessionRemoteFirst");
      log.debug("Bean.echo('testRemoteUnchecked') -> "+bean.echo("testRemoteUnchecked"));
      try
      {
         bean.excluded();
         fail("Was able to call UncheckedSessionRemoteFirst.excluded");
      }
      catch(RemoteException e)
      {
         log.debug("UncheckedSessionRemoteFirst.excluded failed as expected");         
      }
      bean.remove();
      logout();
   }

   /** Test that a user with a role that has not been assigned any
    method permissions in the ejb-jar descriptor is able to access a
    method that has been marked as unchecked.
    */
   public void testUnchecked() throws Exception
   {
      log.debug("+++ testUnchecked");
      // Login as scott to create the bean
      login();
      Object obj = getInitialContext().lookup("spec.StatelessSession");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found spec.StatelessSession Home");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSession");
      // Logout and login back in as stark to test access to the unchecked method
      logout();
      login("stark", "javaman".toCharArray());
      bean.unchecked();
      log.debug("Called Bean.unchecked()");
      logout();
   }

   /** Test that a user with a valid role is able to access a
    bean for which all methods have been marked as unchecked.
    */
   public void testUncheckedWithLogin() throws Exception
   {
      log.debug("+++ testUncheckedWithLogin");
      // Login as scott to see that a user with roles is allowed access
      login();
      Object obj = getInitialContext().lookup("spec.UncheckedSession");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found spec.StatelessSession Home");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSession");
      bean.unchecked();
      log.debug("Called Bean.unchecked()");
      logout();
   }

   /** Test that user scott who has the Echo role is not able to
    access the StatelessSession2.excluded method even though
    the Echo role has been granted access to all methods of
    StatelessSession2 to test that the excluded-list takes
    precendence over the method-permissions.
    */
   public void testExcluded() throws Exception
   {
      log.debug("+++ testExcluded");
      login();
      Object obj = getInitialContext().lookup("spec.StatelessSession2");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found spec.StatelessSession2 Home");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSession2");
      try
      {
         bean.excluded();
         fail("Was able to call Bean.excluded()");
      }
      catch(Exception e)
      {
         log.debug("Bean.excluded() failed as expected");
         // This is what we expect
      }
      logout();
   }
   
   /** This method tests the following call chains:
    1. RunAsStatelessSession.echo() -> PrivateEntity.echo()
    2. RunAsStatelessSession.noop() -> RunAsStatelessSession.excluded()
    3. RunAsStatelessSession.forward() -> StatelessSession.echo()
    1. Should succeed because the run-as identity of RunAsStatelessSession
    is valid for accessing PrivateEntity.
    2. Should succeed because the run-as identity of RunAsStatelessSession
    is valid for accessing RunAsStatelessSession.excluded().
    3. Should fail because the run-as identity of RunAsStatelessSession
    is not Echo.
    */
   public void testRunAs() throws Exception
   {
      log.debug("+++ testRunAs");
      login();
      Object obj = getInitialContext().lookup("spec.RunAsStatelessSession");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found RunAsStatelessSession Home");
      StatelessSession bean = home.create();
      log.debug("Created spec.RunAsStatelessSession");
      log.debug("Bean.echo('testRunAs') -> "+bean.echo("testRunAs"));
      bean.noop();
      log.debug("Bean.noop(), ok");
      
      try
      {
         // This should not be allowed
         bean.forward("Hello");
         fail("Was able to call RunAsStatelessSession.forward");
      }
      catch(RemoteException e)
      {
         log.debug("StatelessSession.forward failed as expected");
      }
      bean.remove();
   }

   /** This method tests the following call chain:
    Level1CallerBean.callEcho() -> Level2CallerBean.invokeEcho()
      -> Level3CalleeBean.echo()
    The Level1CallerBean uses a run-as of InternalRole and the Level2CallerBean
    and Level3CalleeBean are only accessible by InternalRole.
    */   
   public void testDeepRunAs() throws Exception
   {
      log.debug("+++ testDeepRunAs");
      login();
      Object obj = getInitialContext().lookup("spec.Level1CallerBean");
      obj = PortableRemoteObject.narrow(obj, CalledSessionHome.class);
      CalledSessionHome home = (CalledSessionHome) obj;
      log.debug("Found Level1CallerBean Home");
      CalledSession bean = home.create();
      log.debug("Created spec.Level1CallerBean");
      bean.callEcho();
      log.debug("Bean.callEcho() ok");
      bean.remove();

      // Make sure we cannot access Level2CallerBean remotely
      obj = getInitialContext().lookup("spec.Level2CallerBean");
      obj = PortableRemoteObject.narrow(obj, CalledSessionHome.class);
      home = (CalledSessionHome) obj;
      log.debug("Found Level2CallerBean Home");
      try
      {
         bean = home.create();
         fail("Was able to create Level2CallerBean");
      }
      catch(ServerException e)
      {
         AccessException ae = (AccessException) e.detail;
         log.debug("Caught AccessException as expected", ae);
      }
      catch(AccessException e)
      {
         log.debug("Caught AccessException as expected", e);
      }
   }

   public void testRunAsSFSB() throws Exception
   {
      log.info("+++ testRunAsSFSB");
      login();
      Object obj = getInitialContext().lookup("spec.CallerFacadeBean-testRunAsSFSB");
      obj = PortableRemoteObject.narrow(obj, CalledSessionHome.class);
      CalledSessionHome home = (CalledSessionHome) obj;
      log.debug("Found CallerFacadeBean-testRunAsSFSB Home");
      CalledSession bean = home.create();
      log.debug("Created spec.CallerFacadeBean-testRunAsSFSB");
      bean.invokeEcho("testRunAsSFSB");
      log.debug("Bean.invokeEcho() ok");
      bean.remove();
   }

   /**
    * Test the run-as side-effects raised in 
    * http://jira.jboss.com/jira/browse/JBAS-1852
    * 
    * @throws Exception
    */ 
   public void testJBAS1852() throws Exception
   {
      log.info("+++ testJBAS1852");
      login();
      Object obj = getInitialContext().lookup("spec.PublicSessionFacade");
      obj = PortableRemoteObject.narrow(obj, SessionFacadeHome.class);
      SessionFacadeHome home = (SessionFacadeHome) obj;
      log.debug("Found PublicSessionFacade home");
      SessionFacade bean = home.create();
      log.debug("Created PublicSessionFacade");
      log.debug("Bean.callEcho('testJBAS1852') -> " + bean.callEcho("testJBAS1852"));
      bean.remove();
   }

   /** Test that an MDB with a run-as identity is able to access secure EJBs
    that require the identity.
    */
   public void testMDBRunAs() throws Exception
   {
      log.debug("+++ testMDBRunAs");
      logout();
      QueueConnectionFactory queueFactory = (QueueConnectionFactory) getInitialContext().lookup(QUEUE_FACTORY);
      Queue queA = (Queue) getInitialContext().lookup("queue/A");
      Queue queB = (Queue) getInitialContext().lookup("queue/B");
      QueueConnection queueConn = queueFactory.createQueueConnection();
      QueueSession session = queueConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      Message msg = session.createMessage();
      msg.setStringProperty("arg", "testMDBRunAs");
      msg.setJMSReplyTo(queB);
      QueueSender sender = session.createSender(queA);
      sender.send(msg);
      sender.close();
      log.debug("Sent msg to queue/A");
      queueConn.start();
      QueueReceiver recv = session.createReceiver(queB);
      msg = recv.receive(5000);
      log.debug("Recv msg: "+msg);
      String info = msg.getStringProperty("reply");
      recv.close();
      session.close();
      queueConn.close();

      if( info == null || info.startsWith("Failed") )
      {
         fail("Recevied exception reply, info="+info);
      }
   }

   /** Test that an MDB with a run-as identity is able to access secure EJBs
    that require the identity. DeepRunAsMDB -> Level1MDBCallerBean.callEcho() ->
      Level2CallerBean.invokeEcho() -> Level3CalleeBean.echo()
    The MDB uses a run-as of InternalRole and the Level2CallerBean
    and Level3CalleeBean are only accessible by InternalRole.
    */
   public void testMDBDeepRunAs() throws Exception
   {
      log.debug("+++ testMDBDeepRunAs");
      logout();
      QueueConnectionFactory queueFactory = (QueueConnectionFactory) getInitialContext().lookup(QUEUE_FACTORY);
      Queue queD = (Queue) getInitialContext().lookup("queue/D");
      Queue queB = (Queue) getInitialContext().lookup("queue/B");
      QueueConnection queueConn = queueFactory.createQueueConnection();
      QueueSession session = queueConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      Message msg = session.createMessage();
      msg.setStringProperty("arg", "testMDBDeepRunAs");
      msg.setJMSReplyTo(queB);
      QueueSender sender = session.createSender(queD);
      sender.send(msg);
      sender.close();
      log.debug("Sent msg to "+queD);
      queueConn.start();
      QueueReceiver recv = session.createReceiver(queB);
      msg = recv.receive(5000);
      log.debug("Recv msg: "+msg);
      String info = msg.getStringProperty("reply");
      recv.close();
      session.close();
      queueConn.close();

      if( info == null || info.startsWith("Failed") )
      {
         fail("Recevied exception reply, info="+info);
      }
   }

   /** Test the security behavior of a statless handle. To obtain secured bean
    from a handle requires that there be a security context to obtain the ejb.
    */
   public void testHandle() throws Exception
   {
      log.debug("+++ testHandle");
      login();
      Object obj = getInitialContext().lookup("spec.StatelessSession");
      obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
      StatelessSessionHome home = (StatelessSessionHome) obj;
      log.debug("Found StatelessSessionHome");
      StatelessSession bean = home.create();
      log.debug("Created spec.StatelessSession");
      Handle h = bean.getHandle();
      log.debug("Obtained handle: "+h);
      bean = (StatelessSession) h.getEJBObject();
      log.debug("Obtained bean from handle: "+bean);
      log.debug("Bean.echo('testHandle') -> "+bean.echo("testHandle"));
      logout();

      /* Attempting to obtain the EJB fron the handle without security
       association present should fail
      */
      try
      {
         bean = (StatelessSession) h.getEJBObject();
         fail("Should not be able to obtain a bean without login info");
      }
      catch(Exception e)
      {
         log.debug("Obtaining bean from handle failed as expected", e);
      }

      // One should be able to obtain a handle without a login
      h = bean.getHandle();
      login();
      // Now we should be able to obtain and use the secure bean
      bean = (StatelessSession) h.getEJBObject();
      log.debug("Obtained bean from handle: "+bean);
      log.debug("Bean.echo('testHandle2') -> "+bean.echo("testHandle2"));
      logout();
   }

   /** Test the security behavior of stateful handles. To obtain secured bean
    from a handle requires that there be a security context to obtain the ejb.
    */
   public void testStatefulHandle() throws Exception
   {
      log.debug("+++ testStatefulHandle");
      login();
      Object obj = getInitialContext().lookup("spec.StatefulSession");
      obj = PortableRemoteObject.narrow(obj, StatefulSessionHome.class);
      StatefulSessionHome home = (StatefulSessionHome) obj;
      log.debug("Found StatefulSession");
      StatefulSession bean = home.create("testStatefulHandle");
      log.debug("Created spec.StatelessSession");
      Handle h = bean.getHandle();
      log.debug("Obtained handle: "+h);
      bean = (StatefulSession) h.getEJBObject();
      log.debug("Obtained bean from handle: "+bean);
      log.debug("Bean.echo('Hello') -> "+bean.echo("Hello"));
      logout();

      /* Attempting to obtain the EJB fron the handle without security
       association present should fail
      */
      try
      {
         bean = (StatefulSession) h.getEJBObject();
         fail("Should not be able to obtain a bean without login info");
      }
      catch(Exception e)
      {
         log.debug("Obtaining bean from handle failed as expected, e="+e.getMessage());
      }

      // One should be able to obtain a handle without a login
      h = bean.getHandle();
      login();
      // Now we should be able to obtain and use the secure bean
      bean = (StatefulSession) h.getEJBObject();
      log.debug("Obtained bean from handle: "+bean);
      log.debug("Bean.echo('Hello') -> "+bean.echo("Hello"));
      logout();
   }

   /** Stress test declarative security.
    */
   public void testStress() throws Exception
   {
      log.debug("+++ testStress");
      int count = Integer.getInteger("jbosstest.threadcount", 10).intValue();
      int iterations = Integer.getInteger("jbosstest.iterationcount", 5000).intValue();
      // Use a minimum of 100 iterations
      if( iterations < 100 )
         iterations = 100;
      log.info("Creating "+count+" threads doing "+iterations+" iterations");
      Thread[] testThreads = new Thread[count];
      StressTester[] testers = new StressTester[count];
      
      for(int t = 0; t < count; t ++)
      {
         StressTester test = new StressTester(getInitialContext(), iterations);
         testers[t] = test;
         Thread thr = new Thread(test, "Tester#"+t);
         thr.start();
         testThreads[t] = thr;
      }

      int errorCount = 0;
      for(int t = 0; t < count; t ++)
      {
         Thread thr = testThreads[t];
         thr.join();
         StressTester test = testers[t];
         if( test.error != null )
         {
            errorCount ++;
         }
      }
      assertTrue("Thread error count == 0", errorCount == 0);
   }

   /** Stress test declarative security with the JAAS cache disabled.
    */
   public void testStressNoJaasCache() throws Exception
   {
      log.info("+++ testStressNoJaasCache, domain=spec-test");
      // Disable caching for the spec-test domain
      MBeanServerConnection conn = (MBeanServerConnection) getServer();
      ObjectName secMgrName = new ObjectName("jboss.security:service=JaasSecurityManager");
      JaasSecurityManagerServiceMBean secMgr = (JaasSecurityManagerServiceMBean)
         MBeanServerInvocationHandler.newProxyInstance(conn, secMgrName,
         JaasSecurityManagerServiceMBean.class, false);
      secMgr.setCacheTimeout("spec-test", 0, 0);

      Exception failed = null;
      try
      {
         // Now execute the testStress access
         testStress();
      }
      catch(Exception e)
      {
         failed = e;
      }

      secMgr.setCacheTimeout("spec-test", 60, 60);
      if( failed != null )
         throw failed;
   }

   private static class StressTester implements Runnable
   {
      InitialContext ctx;
      int iterations;
      Throwable error;

      StressTester(InitialContext ctx, int iterations) throws Exception
      {
         this.ctx = ctx;
         this.iterations = iterations;
      }
      public void run()
      {
         Thread t = Thread.currentThread();
         Logger log = Logger.getLogger(t.getName());
         log.info("Begin run, t="+t);
         try
         {
            AppCallbackHandler handler = new AppCallbackHandler(EJBSpecUnitTestCase.username,
               EJBSpecUnitTestCase.password);
            for(int i = 0; i < iterations; i ++)
            {
               LoginContext lc = new LoginContext("spec-test-multi-threaded", handler);
               lc.login();
               Object obj = ctx.lookup("spec.StatelessSession");
               obj = PortableRemoteObject.narrow(obj, StatelessSessionHome.class);
               StatelessSessionHome home = (StatelessSessionHome) obj;
               log.debug("Found StatelessSessionHome");
               StatelessSession bean = home.create();
               log.debug("Created spec.StatelessSession");
               log.debug("Bean.echo('Hello') -> "+bean.echo("Hello"));
               bean.remove();
               lc.logout();
            }
         }
         catch(Throwable e)
         {
            error = e;
            log.error("Security failure", e);
         }
         log.info("End run, t="+Thread.currentThread());
      }
   }

   /** Login as user scott using the conf.name login config or
    'spec-test' if conf.name is not defined.
    */
   private void login() throws Exception
   {
      login(username, password);
   }
   private void login(String username, char[] password) throws Exception
   {
      if( loggedIn )
         return;
      
      lc = null;
      String confName = System.getProperty("conf.name", "spec-test");
      AppCallbackHandler handler = new AppCallbackHandler(username, password);
      log.debug("Creating LoginContext("+confName+")");
      lc = new LoginContext(confName, handler);
      lc.login();
      log.debug("Created LoginContext, subject="+lc.getSubject());
      loggedIn = true;
   }
   private void logout() throws Exception
   {
      if( loggedIn )
      {
         loggedIn = false;
         lc.logout();
      }
   }


   /**
    * Setup the test suite.
    */
   public static Test suite() throws Exception
   {
      TestSuite suite = new TestSuite();
      suite.addTest(new TestSuite(EJBSpecUnitTestCase.class));

      // Create an initializer for the test suite
      TestSetup wrapper = new JBossTestSetup(suite)
      {
         protected void setUp() throws Exception
         {
            super.setUp();
            Configuration.setConfiguration(new XMLLoginConfigImpl());
            redeploy("security-spec.jar");
            flushAuthCache();
         }
         protected void tearDown() throws Exception
         {
            undeploy("security-spec.jar");
            super.tearDown();
         
         }
      };
      return wrapper;
   }

}
