/*
 * JBoss, Home of Professional Open Source
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * 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.profileservice.test;

import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.jboss.deployers.spi.management.ManagementView;
import org.jboss.deployers.spi.management.deploy.DeploymentManager;
import org.jboss.deployers.spi.management.deploy.DeploymentProgress;
import org.jboss.deployers.spi.management.deploy.ProgressEvent;
import org.jboss.deployers.spi.management.deploy.ProgressListener;
import org.jboss.managed.api.ComponentType;
import org.jboss.managed.api.ManagedComponent;
import org.jboss.managed.api.ManagedDeployment;
import org.jboss.managed.api.ManagedOperation;
import org.jboss.managed.api.ManagedParameter;
import org.jboss.managed.api.ManagedProperty;

/**
 * <p>
 * Profile service security tests.
 * </p>
 * 
 * @author Scott.Stark@jboss.org
 * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
 * @version $Revision: 101688 $
 */
public class SecurityManagedObjectsTestCase extends AbstractProfileServiceTest
{
   protected ProgressListener listener = new TestProgressListener();

   /**
    * <p>
    * Creates an instance of {@code SecurityManagedObjectsTestCase} with the specified name.
    * </p>
    * 
    * @param name a {@code String} representing the name of this {@code TestCase}.
    */
   public SecurityManagedObjectsTestCase(String name)
   {
      super(name);
   }

   /**
    * Looks for ComponentType("MCBean", "Security") type and logs them.
    * 
    * @exception thrown if there are no matching components
    */
   public void testSecurityMCBeans() throws Exception
   {
      ManagementView managementView = getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      Set<ManagedComponent> mcs = managementView.getComponentsForType(type);
      assertTrue("There are MCBean,Security components", mcs.size() > 0);
      super.getLog().debug("MCBeans: " + mcs);
   }

   /**
    * <p>
    * Validates at the {@code SecurityConfig} managed component.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testSecurityConfig() throws Exception
   {
      ManagementView managementView = getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent component = managementView.getComponent("SecurityConfig", type);
      assertNotNull(component);

      // verify that the component has the expected properties.
      Map<String, ManagedProperty> properties = component.getProperties();
      assertNotNull(properties);
      assertEquals("Unexpected number of properties", 4, properties.size());
      assertTrue("Missing expected property: loginConfig", properties.containsKey("loginConfig"));
      assertTrue("Missing expected property: mbeanServer", properties.containsKey("mbeanServer"));
      assertTrue("Missing expected property: defaultLoginConfig", properties.containsKey("defaultLoginConfig"));
      assertTrue("Missing expected property: state", properties.containsKey("state"));

      // verify that the component has the expected operations.
      String[] expectedOperations = {"startService", "stopService", "pushLoginConfig", "popLoginConfig"};
      Set<ManagedOperation> operations = component.getOperations();
      assertEquals("Unexpected number of operations", expectedOperations.length, operations.size());
      // copy the names of the operations to a new collection to compare them with the expected names.
      Set<String> operationNames = new HashSet<String>();
      for (ManagedOperation operation : operations)
         operationNames.add(operation.getName());
      for (String expectedOperation : expectedOperations)
         assertTrue("Expected operation " + expectedOperation + " not found", operationNames
               .contains(expectedOperation));
   }

   /**
    * <p>
    * Validates the {@code XMLLoginConfig} managed component.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testXMLLoginConfig() throws Exception
   {
      // get the XMLLoginConfig managed component.
      ManagementView managementView = super.getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent component = managementView.getComponent("XMLLoginConfig", type);
      assertNotNull(component);

      // verify that the component has the expected properties.
      Map<String, ManagedProperty> properties = component.getProperties();
      assertNotNull(properties);
      assertEquals("Unexpected number of properties", 3, properties.size());
      assertTrue("Missing expected property: configURL", properties.containsKey("configURL"));
      assertTrue("Missing expected property: validateDTD", properties.containsKey("validateDTD"));
      assertTrue("Missing expected property: state", properties.containsKey("state"));

      // verify that the component has the expected operations.
      String[] expectedOperations = {"loadConfig", "setConfigResource", "displayAppConfig", "addApplicationPolicy",
            "removeConfigs", "getApplicationPolicy", "getConfiguration"};
      Set<ManagedOperation> operations = component.getOperations();
      assertEquals("Unexpected number of operations", expectedOperations.length, operations.size());
      // copy the names of the operations to a new collection to compare them with the expected names.
      Set<String> operationNames = new HashSet<String>();
      for (ManagedOperation operation : operations)
         operationNames.add(operation.getName());
      for (String expectedOperation : expectedOperations)
         assertTrue("Expected operation " + expectedOperation + " not found", operationNames
               .contains(expectedOperation));
   }

   /**
    * <p>
    * Validates the {@code JBossSecuritySubjectFactory} managed component.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testJBossSecuritySubjectFactory() throws Exception
   {
      // get the XMLLoginConfig managed component.
      ManagementView managementView = super.getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent component = managementView.getComponent("JBossSecuritySubjectFactory", type);
      assertNotNull(component);

      // verify that the component has the expected properties.
      Map<String, ManagedProperty> properties = component.getProperties();
      assertNotNull(properties);
      assertEquals("Unexpected number of properties", 2, properties.size());
      assertTrue("Missing expected property: securityManagement", properties.containsKey("securityManagement"));
      assertTrue("Missing expected property: state", properties.containsKey("state"));

      // this component should have 2 createSubject operations - one with no args and one with a String arg.
      boolean foundNoArgOperation = false;
      boolean foundStringArgOperation = false;
      Set<ManagedOperation> operations = component.getOperations();
      assertEquals("Unexpected number of operations", 2, operations.size());
      for (ManagedOperation operation : operations)
      {
         assertEquals("Unexpected operation found", "createSubject", operation.getName());
         ManagedParameter[] parameters = operation.getParameters();
         if (parameters.length == 0)
         {
            foundNoArgOperation = true;
         }
         else
         {
            assertEquals("Unexpected number of parameters", 1, parameters.length);
            ManagedParameter parameter = parameters[0];
            assertEquals("Invalid parameter name", "securityDomainName", parameter.getName());
            assertEquals("Invalid parameter type", "java.lang.String", parameter.getMetaType().getTypeName());
            foundStringArgOperation = true;
         }
      }
      assertTrue(foundNoArgOperation);
      assertTrue(foundStringArgOperation);
   }

   /**
    * <p>
    * Validates the {@code JNDIContextEstablishment} managed component.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testJNDIContextEstablishment() throws Exception
   {
      // get the XMLLoginConfig managed component.
      ManagementView managementView = super.getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent component = managementView.getComponent("JNDIContextEstablishment", type);
      assertNotNull(component);

      // verify that the component has the expected properties.
      Map<String, ManagedProperty> properties = component.getProperties();
      assertNotNull(properties);
      assertEquals("Unexpected number of properties", 3, properties.size());
      assertTrue("Missing expected property: baseContext", properties.containsKey("baseContext"));
      assertTrue("Missing expected property: factoryName", properties.containsKey("factoryName"));
      assertTrue("Missing expected property: state", properties.containsKey("state"));

      // verify that the component has the expected operations.
      Set<ManagedOperation> operations = component.getOperations();
      assertEquals("Unexpected number of operations", 0, operations.size());
   }

   /**
    * <p>
    * Validates the {@code JNDIBasedSecurityManagement} managed component.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testJNDIBasedSecurityManagement() throws Exception
   {
      // get the XMLLoginConfig managed component.
      ManagementView managementView = super.getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent component = managementView.getComponent("JNDIBasedSecurityManagement", type);
      assertNotNull(component);

      // verify that the component has the expected managed properties.
      Map<String, ManagedProperty> properties = component.getProperties();
      assertNotNull(properties);
      assertEquals("Unexpected number of properties", 6, properties.size());
      assertTrue("Missing expected property: authenticationMgrClass", properties.containsKey("authenticationMgrClass"));
      assertTrue("Missing expected property: authorizationMgrClass", properties.containsKey("authorizationMgrClass"));
      assertTrue("Missing expected property: auditMgrClass", properties.containsKey("auditMgrClass"));
      assertTrue("Missing expected property: identityTrustMgrClass", properties.containsKey("identityTrustMgrClass"));
      assertTrue("Missing expected property: mappingMgrClass", properties.containsKey("mappingMgrClass"));
      assertTrue("Missing expected property: state", properties.containsKey("state"));

      // verify that the component has the expected managed operations.
      String[] opsWithStringParam = {"getAuthenticationManager", "getAuthorizationManager", "getAuditManager",
            "getIdentityTrustManager", "getMappingManager", "createSecurityDomainContext",
            "deregisterJaasSecurityDomainInstance"};
      String[] opsWithDomainParam = {"registerJaasSecurityDomainInstance"};
      Map<String, ManagedOperation> operations = new HashMap<String, ManagedOperation>();
      for (ManagedOperation operation : component.getOperations())
         operations.put(operation.getName(), operation);
      assertEquals("Unexpected number of operations", opsWithStringParam.length + opsWithDomainParam.length, operations
            .size());

      // first check that all operations that receive a security domain String are present.
      for (String operationName : opsWithStringParam)
      {
         ManagedOperation operation = operations.get(operationName);
         assertNotNull("Missing expected operation: " + operationName, operation);
         ManagedParameter[] parameters = operation.getParameters();
         assertEquals("Unexpected number of parameters", 1, parameters.length);
         assertEquals("Invalid parameter name", "securityDomain", parameters[0].getName());
         assertEquals("Invalid parameter type", "java.lang.String", parameters[0].getMetaType().getTypeName());
      }

      // now check that the operations that receive a JaasSecurityDomain are present.
      for (String operationName : opsWithDomainParam)
      {
         ManagedOperation operation = operations.get(operationName);
         assertNotNull("Missing expected operation: " + operationName, operation);
         ManagedParameter[] parameters = operation.getParameters();
         assertEquals("Unexpected number of parameters", 1, parameters.length);
         assertEquals("Invalid parameter name", "domain", parameters[0].getName());
         assertEquals("Invalid parameter type", "org.jboss.security.plugins.JaasSecurityDomain", parameters[0]
               .getMetaType().getTypeName());
      }
   }

   /**
    * <p>
    * Validates {@code JaasSecurityDomain} managed objects that are created by deploying a
    * {@code testdomains-jboss-beans.xml} file.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testJaasSecurityDomain() throws Exception
   {
      // deploy the XML file that contains the test security domains.
      String domainsDeployment = "testdomains-jboss-beans.xml";
      this.deployResource(domainsDeployment, "profileservice/security/" + domainsDeployment);

      // validate the managed deployment.
      ManagementView managementView = getManagementView();
      
      ManagedDeployment deployment = managementView.getDeployment(domainsDeployment);
      assertNotNull(deployment);
      // verify the deployment contains the expected managed components.
      assertEquals("Unexpected number of components", 2, deployment.getComponents().size());
      assertNotNull("Missing expected component: TestDomain1", deployment.getComponent("TestDomain1"));
      assertNotNull("Missing expected component: TestDomain2", deployment.getComponent("TestDomain2"));

      // validate the components created upon deployment.
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent component = managementView.getComponent("TestDomain1", type);
      assertNotNull(component);

      // verify that the component has the expected managed properties.
      Map<String, ManagedProperty> properties = component.getProperties();
      assertNotNull(properties);
      assertEquals("Unexpected number of properties", 22, properties.size());
      assertTrue("Missing expected property: securityDomain", properties.containsKey("securityDomain"));
      assertTrue("Missing expected property: state", properties.containsKey("state"));
      // keystore and truststore configuration properties.
      assertTrue("Missing expected property: keyStoreType", properties.containsKey("keyStoreType"));
      assertTrue("Missing expected property: keyStoreURL", properties.containsKey("keyStoreURL"));
      assertTrue("Missing expected property: keyStorePass", properties.containsKey("keyStorePass"));
      assertTrue("Missing expected property: keyStoreAlias", properties.containsKey("keyStoreAlias"));
      assertTrue("Missing expected property: keyStoreProviderArgument", properties.containsKey("keyStoreProviderArgument"));
      assertTrue("Missing expected property: trustStoreType", properties.containsKey("trustStoreType"));
      assertTrue("Missing expected property: trustStoreURL", properties.containsKey("trustStoreURL"));
      assertTrue("Missing expected property: trustStorePass", properties.containsKey("trustStorePass"));
      assertTrue("Missing expected property: trustStoreProviderArgument", properties.containsKey("trustStoreProviderArgument"));
      // security manager service injection properties.
      assertTrue("Missing expected property: managerServiceName", properties.containsKey("managerServiceName"));
      assertTrue("Missing expected property: securityManagement", properties.containsKey("securityManagement"));
      // cipher algorithm properties.
      assertTrue("Missing expected property: salt", properties.containsKey("salt"));
      assertTrue("Missing expected property: iterationCount", properties.containsKey("iterationCount"));
      assertTrue("Missing expected property: cipherAlgorithm", properties.containsKey("cipherAlgorithm"));

      // verify that the component has the expected managed operations.
      Map<String, ManagedOperation> operations = new HashMap<String, ManagedOperation>();
      for (ManagedOperation operation : component.getOperations())
         operations.put(operation.getName(), operation);
      String[] noArgsOperations = {"getKeyStore", "getTrustStore", "getKeyManagerFactory", "getTrustManagerFactory",
            "reloadKeyAndTrustStore"};
      String[] oneArgOperations = {"encode", "decode", "encode64", "decode64"};
      assertEquals("Unexpected number of operations", noArgsOperations.length + oneArgOperations.length, operations
            .size());
      // first check the methods that don't have any parameter.
      for(String operationName : noArgsOperations)
      {
         ManagedOperation operation = operations.get(operationName);
         assertNotNull("Unexpected operation name: " + operationName, operation);
         ManagedParameter[] parameters = operation.getParameters();
         assertEquals("Unexpected number of parameters", 0, parameters.length);
      }
      // now check the methods that contain a 'secret' parameter.
      for(String operationName : oneArgOperations)
      {
         ManagedOperation operation = operations.get(operationName);
         assertNotNull("Unexpected operation name: " + operationName, operation);
         ManagedParameter[] parameters = operation.getParameters();
         assertEquals("Unexpected number of parameters", 1, parameters.length);
         assertEquals("Invalid parameter name", "secret", parameters[0].getName());
      }
      
      // just the check the second security domain is also available - we don't repeat the tests because the
      // properties and operations must be the same of those verified in the first domain.
      component = managementView.getComponent("TestDomain2", type);
      assertNotNull(component);

      // undeploy the test security domains.
      this.undeployResource(domainsDeployment);
   }

   /**
    * <p>
    * Validates the {@code JNDIBasedSecurityRegistration} managed component.
    * </p>
    * 
    * @throws Exception if an error occurs while running the test.
    */
   public void testJNDIBasedSecurityRegistration() throws Exception
   {
      ManagementView mgtMview = getManagementView();
      ComponentType type = new ComponentType("MCBean", "Security");
      ManagedComponent mc = mgtMview.getComponent("JNDIBasedSecurityRegistration", type);
      assertNotNull(mc);
   }

   /**
    * <p>
    * Deploy a resource, registering it with the profile service.
    * </p>
    * 
    * @param resourceName a {@code String} representing the resource's unique name. This will be the name the resource
    *            will have when it is 'copied' to the server/partition/deploy directory.
    * @param resourcePath the path to the resource that will be deployed.
    * @throws Exception if an error occurs while deploying the resource.
    */
   private void deployResource(String resourceName, String resourcePath) throws Exception
   {
      // create a URL for the resource.
      String contentURLString = super.getResourceURL(resourcePath);
      int index = contentURLString.indexOf(":/");
      URL contentURL = new URL("file" + contentURLString.substring(index));

      // distribute the resource deployment.
      DeploymentManager manager = super.getDeploymentManager();
      DeploymentProgress progress = manager.distribute(resourceName, contentURL, true);
      progress.addProgressListener(this.listener);
      progress.run();
      
      assertDeployed(progress);

      // start the deployment.
      String[] uploadedNames = progress.getDeploymentID().getRepositoryNames();
      progress = manager.start(uploadedNames);
      progress.addProgressListener(this.listener);
      progress.run();
      
      assertDeployed(progress);
   }
   
   private void assertDeployed(DeploymentProgress progress)
   {
      if(progress.getDeploymentStatus().isFailed())
      {
         fail("deployment failed: " + progress.getDeploymentStatus().getFailure());
      }      
   }

   /**
    * <p>
    * Undeploys the specified resource.
    * </p>
    * 
    * @param resourceName the resource's unique name. This must match the {@code resourceName} used when deploying the
    *            resource.
    * @throws Exception if an error occurs while undeploying the resource.
    */
   private void undeployResource(String resourceName) throws Exception
   {
      // stop the resource deployment.
      DeploymentManager manager = super.getDeploymentManager();
      DeploymentProgress progress = manager.stop(resourceName);
      progress.addProgressListener(this.listener);
      progress.run();

      // undeploy the resource.
      progress = manager.remove(resourceName);
      progress.addProgressListener(this.listener);
      progress.run();
   }

   /**
    * <p>
    * Simple {@code ProgressListener} that logs progress events.
    * </p>
    * 
    * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
    */
   private class TestProgressListener implements ProgressListener
   {

      /*
       * (non-Javadoc)
       * 
       * @see org.jboss.deployers.spi.management.deploy.ProgressListener#progressEvent(org.jboss.deployers.spi.management.deploy.ProgressEvent)
       */
      public void progressEvent(ProgressEvent eventInfo)
      {
         log.trace("Received progress event: " + eventInfo);
      }
   }
}
