/*
 * Copyright (C) The MX4J Contributors.
 * All rights reserved.
 *
 * This software is distributed under the terms of the MX4J License version 1.0.
 * See the terms of the MX4J License in the documentation provided with this software.
 */

package test.javax.management;

import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.SecurityPermission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanPermission;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerFactory;
import javax.management.MBeanServerPermission;
import javax.management.ObjectName;
import javax.management.loading.ClassLoaderRepository;
import javax.management.loading.MLet;

import junit.framework.TestCase;
import mx4j.log.Log;
import mx4j.server.MX4JMBeanServer;

/**
 * @version $Revision: 1.5 $
 */
public class LocalSecurityManagerTest extends SecurityManagerTestCase
{
   static
   {
      // For the way JUnit works, we have one JVM per test class
      Policy.setPolicy(new LocalModifiablePolicy());
      System.setSecurityManager(new SecurityManager());
   }

   public LocalSecurityManagerTest(String s)
   {
      super(s);
   }

   protected void setUp() throws Exception
   {
      // Be sure we have a security manager and the right policy
      SecurityManager sm = System.getSecurityManager();
      if (sm == null) fail();
      Policy policy = Policy.getPolicy();
      if (!(policy instanceof LocalModifiablePolicy)) fail();
      ((LocalModifiablePolicy)policy).initialize();
   }

   protected void addPermission(Permission p)
   {
      LocalModifiablePolicy policy = (LocalModifiablePolicy)Policy.getPolicy();
      policy.addPermission(p);
   }

   protected void resetPermissions()
   {
      LocalModifiablePolicy policy = (LocalModifiablePolicy)Policy.getPolicy();
      policy.initialize();
   }

   public void testNewMBeanServer() throws Exception
   {
      try
      {
         MBeanServerFactory.newMBeanServer();
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServerFactory.newMBeanServer();

      try
      {
         MBeanServerFactory.createMBeanServer();
         fail();
      }
      catch (SecurityException ignored)
      {
      }
   }

   public void testCreateMBeanServer() throws Exception
   {
      try
      {
         MBeanServerFactory.createMBeanServer();
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanServerPermission("createMBeanServer"));
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      MBeanServerFactory.newMBeanServer();

      try
      {
         MBeanServerFactory.releaseMBeanServer(server);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      // Clean up
      addPermission(new MBeanServerPermission("releaseMBeanServer"));
      MBeanServerFactory.releaseMBeanServer(server);
   }

   public void testReleaseMBeanServer() throws Exception
   {
      addPermission(new MBeanServerPermission("createMBeanServer"));
      MBeanServer server = MBeanServerFactory.createMBeanServer();

      try
      {
         MBeanServerFactory.releaseMBeanServer(server);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanServerPermission("releaseMBeanServer"));
      MBeanServerFactory.releaseMBeanServer(server);
   }

   public void testReleaseMBeanServer2() throws Exception
   {
      addPermission(new MBeanServerPermission("createMBeanServer, releaseMBeanServer"));
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      MBeanServerFactory.releaseMBeanServer(server);
   }

   public void testFindMBeanServer() throws Exception
   {
      addPermission(new MBeanServerPermission("createMBeanServer"));
      MBeanServer server = MBeanServerFactory.createMBeanServer();

      try
      {
         MBeanServerFactory.findMBeanServer(null);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanServerPermission("findMBeanServer"));
      ArrayList list = MBeanServerFactory.findMBeanServer(null);
      if (!list.contains(server)) fail();

      // Clean up
      addPermission(new MBeanServerPermission("releaseMBeanServer"));
      MBeanServerFactory.releaseMBeanServer(server);
   }

   public void testAddRemoveNotificationListener() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testAddRemoveNotificationListener(server);
   }

   public void testCreateMBean4Params() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testCreateMBean4Params(server);
   }

   public void testCreateMBean5Params() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testCreateMBean5Params(server);
   }

   public void testGetAttribute() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testGetAttribute(server);
   }

   public void testGetAttributes() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testGetAttributes(server);
   }

   public void testGetDefaultDomain() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      String domain = "simon";
      MBeanServer server = MBeanServerFactory.newMBeanServer(domain);
      testGetDefaultDomain(server, domain);
   }

   public void testGetDomains() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testGetDomains(server);
   }

   public void testGetMBeanCount() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testGetMBeanCount(server);
   }

   public void testGetMBeanInfo() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testGetMBeanInfo(server);
   }

   public void testGetObjectInstance() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testGetObjectInstance(server);
   }

   public void testInstantiate() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();

      String className = "java.lang.String";

      try
      {
         server.instantiate(className, null);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanPermission(className, "instantiate"));
      server.instantiate(className, null);

      // Check also the overloaded version, we need an MLet
      String mletClassName = "javax.management.loading.MLet";
      ObjectName name = new ObjectName(server.getDefaultDomain(), "mbean", "mlet");
      resetPermissions();
      addPermission(new MBeanPermission(mletClassName, "instantiate, registerMBean"));
      addPermission(new RuntimePermission("createClassLoader"));
      server.createMBean(mletClassName, name, null);

      try
      {
         server.instantiate(className, null);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanPermission(className, "instantiate"));
      server.instantiate(className, null);
   }

   public void testInvoke() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testInvoke(server);
   }

   public void testIsInstanceOf() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testIsInstanceOf(server);
   }

   public void testIsRegistered() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testIsRegistered(server);
   }

   public void testQueryMBeans() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testQueryMBeans(server);
   }

   public void testQueryNames() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testQueryNames(server);
   }

   public void testRegisterMBean() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();

      MBeanServerDelegate mbean = new MBeanServerDelegate();
      ObjectName name = new ObjectName(server.getDefaultDomain(), "name", "test");

      try
      {
         server.registerMBean(mbean, name);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanPermission(mbean.getClass().getName() + "[" + name.getCanonicalName() + "]", "registerMBean"));
      server.registerMBean(mbean, name);
   }

   public void testSetAttribute() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testSetAttribute(server);
   }

   public void testSetAttributes() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testSetAttributes(server);
   }

   public void testUnregisterMBean() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      testUnregisterMBean(server);
   }

   public void testGetClassLoader() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();

      // Needed to create an MLet, which is a ClassLoader
      addPermission(new RuntimePermission("createClassLoader"));
      ObjectName name = new ObjectName(server.getDefaultDomain(), "mbean", "mlet");
      MLet mlet = new MLet();
      addPermission(new MBeanPermission(mlet.getClass().getName(), "registerMBean"));
      server.registerMBean(mlet, name);

      try
      {
         server.getClassLoader(null);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      // Dummy class
      addPermission(new MBeanPermission("foo[" + name.getCanonicalName() + "]", "getClassLoader"));

      try
      {
         server.getClassLoader(name);
         fail();
      }
      catch (SecurityException ignored)
      {
      }

      addPermission(new MBeanPermission(mlet.getClass().getName() + "[" + name.getCanonicalName() + "]", "getClassLoader"));
      ClassLoader result = server.getClassLoader(name);
      assertSame(result, mlet);
   }

   public void testGetClassLoaderFor() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      ObjectName delegate = ObjectName.getInstance("JMImplementation", "type", "MBeanServerDelegate");
      try
      {
         server.getClassLoaderFor(delegate);
         fail();
      }
      catch (SecurityException x)
      {
      }
      addPermission(new MBeanPermission("[" + delegate.getCanonicalName() + "]", "getClassLoaderFor"));
      ClassLoader loader = server.getClassLoaderFor(delegate);
      assertNotNull(loader);
   }

   public void testGetClassLoaderRepository() throws Exception
   {
      addPermission(new MBeanServerPermission("newMBeanServer"));
      MBeanServer server = MBeanServerFactory.newMBeanServer();
      try
      {
         server.getClassLoaderRepository();
         fail();
      }
      catch (SecurityException x)
      {
      }
      addPermission(new MBeanPermission("*", "getClassLoaderRepository"));
      ClassLoaderRepository loader = server.getClassLoaderRepository();
      assertNotNull(loader);
   }

   /**
    * A modifiable policy that allow permissions to be added at runtime, used for tests purposes only.
    */
   public static class LocalModifiablePolicy extends Policy
   {
      private final ProtectionDomain testDomain;
      private final Map permissionsMap = new HashMap();

      public LocalModifiablePolicy()
      {
         // Here we still have no security manager installed
         testDomain = LocalModifiablePolicy.class.getProtectionDomain();

         // Add the permissions needed to run the tests
         CodeSource junitCodeSource = TestCase.class.getProtectionDomain().getCodeSource();
         permissionsMap.put(junitCodeSource, createAllPermissions());

         CodeSource mx4jCodeSource = MBeanServerFactory.class.getProtectionDomain().getCodeSource();
         permissionsMap.put(mx4jCodeSource, createAllPermissions());

         CodeSource implCodeSource = MX4JMBeanServer.class.getProtectionDomain().getCodeSource();
         permissionsMap.put(implCodeSource, createAllPermissions());

         // In the automated tests, the log classes may be taken from the JSR 160 jars.
         CodeSource logCodeSource = Log.class.getProtectionDomain().getCodeSource();
         permissionsMap.put(logCodeSource, createAllPermissions());

         ClassLoader loader = getClass().getClassLoader();

         // BCEL
         try
         {
            Class cls = loader.loadClass("org.apache.bcel.generic.Type");
            CodeSource bcelCodeSource = cls.getProtectionDomain().getCodeSource();
            permissionsMap.put(bcelCodeSource, createAllPermissions());
         }
         catch (ClassNotFoundException ignored)
         {
         }


         // When we run automated, we need also permissions for Ant jars
         try
         {
            Class cls = loader.loadClass("org.apache.tools.ant.Task");
            CodeSource antCodeSource = cls.getProtectionDomain().getCodeSource();
            permissionsMap.put(antCodeSource, createAllPermissions());
            cls = loader.loadClass("org.apache.tools.ant.taskdefs.optional.junit.JUnitTask");
            antCodeSource = cls.getProtectionDomain().getCodeSource();
            permissionsMap.put(antCodeSource, createAllPermissions());
         }
         catch (ClassNotFoundException ignored)
         {
         }

         initialize();
      }

      private Permissions createAllPermissions()
      {
         Permissions allPermissions = new Permissions();
         allPermissions.add(new AllPermission());
         return allPermissions;
      }

      public PermissionCollection getPermissions(CodeSource codesource)
      {
         Permissions permissions = (Permissions)permissionsMap.get(codesource);
         if (permissions == null)
         {
            permissions = new Permissions();
            permissionsMap.put(codesource, permissions);
         }
         return permissions;
      }

      public void refresh()
      {
      }

      /**
       * For JDK 1.4 overriding this method disables caching of Permissions done by the
       * standard Policy implementation.
       * This is done because we install this policy *before* installing the security manager.
       * By doing so, in JDK 1.4 the permissions granted at the moment of policy installation
       * (no security manager == AllPermission) are cached and will invalidate all tests, since
       * they will become unmodifiable.
       * <p/>
       * The stack trace is when checking a permission is:
       * <p/>
       * SecurityManager.checkPermission()
       * AccessController.checkPermission()
       * AccessControlContext.checkPermission()
       * ProtectionDomain.implies()
       * LocalModifiablePolicy.implies()
       */
      public boolean implies(ProtectionDomain domain, Permission permission)
      {
         PermissionCollection perms = getPermissions(domain.getCodeSource());
         return perms.implies(permission);
      }

      /**
       * Adds the given permission to the client (the test in this case) codesource
       */
      public void addPermission(Permission p)
      {
         Permissions permissions = (Permissions)getPermissions(testDomain.getCodeSource());
         permissions.add(p);
      }

      /**
       * Initializes the permissions for the client (the test in this case) codesource
       */
      public synchronized void initialize()
      {
         Permissions permissions = new Permissions();
         permissions.add(new SecurityPermission("getPolicy"));
         permissionsMap.put(testDomain.getCodeSource(), permissions);
      }
   }
}
