package org.infinispan.security;

import static org.testng.AssertJUnit.assertEquals;

import java.io.Serializable;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;

import javax.security.auth.Subject;

import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.distexec.DefaultExecutorService;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.security.impl.IdentityRoleMapper;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestingUtil;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;

/**
 * ExecutionAuthorizationTest.
 *
 * @author Tristan Tarrant
 * @since 7.0
 */
@Test(groups = "functional", testName = "security.ExecutionAuthorizationTest")
public class ExecutionAuthorizationTest extends MultipleCacheManagersTest {
   private static final String EXECUTION_CACHE = "executioncache";
   private static Subject ADMIN = TestingUtil.makeSubject("admin");
   private static Subject EXEC = TestingUtil.makeSubject("exec");
   private static Subject NOEXEC = TestingUtil.makeSubject("noexec");



   @Override
   protected void createCacheManagers() throws Throwable {
      final ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true);
      builder.security().authorization().enable().role("admin").role("exec").role("noexec");
      Subject.doAs(ADMIN, (PrivilegedAction<Void>) () -> {
         addClusterEnabledCacheManager(getSecureClusteredGlobalConfiguration(), builder);
         addClusterEnabledCacheManager(getSecureClusteredGlobalConfiguration(), builder);
         for (EmbeddedCacheManager cm : cacheManagers) {
            cm.defineConfiguration(EXECUTION_CACHE, builder.build());
            cm.getCache(EXECUTION_CACHE);
         }
         waitForClusterToForm(EXECUTION_CACHE);
         return null;
      });
   }

   private GlobalConfigurationBuilder getSecureClusteredGlobalConfiguration() {
      GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
      global.security().authorization()
         .enable()
         .principalRoleMapper(new IdentityRoleMapper())
         .role("admin")
            .permission(AuthorizationPermission.ALL)
         .role("exec")
            .permission(AuthorizationPermission.READ)
            .permission(AuthorizationPermission.WRITE)
            .permission(AuthorizationPermission.EXEC)
         .role("noexec")
            .permission(AuthorizationPermission.READ)
            .permission(AuthorizationPermission.WRITE);
      return global;
   }

   @Override
   @AfterClass(alwaysRun = true)
   protected void destroy() {
      Subject.doAs(ADMIN, (PrivilegedAction<Void>) () -> {
         ExecutionAuthorizationTest.super.destroy();
         return null;
      });
   }

   @Override
   @AfterClass(alwaysRun = true)
   protected void clearContent() throws Exception {
      Subject.doAs(ADMIN, (PrivilegedExceptionAction<Void>) () -> {
         try {
            ExecutionAuthorizationTest.super.clearContent();
         } catch (Throwable e) {
            throw new Exception(e);
         }
         return null;
      });
   }

   private void distExecTest() throws Exception {
      DefaultExecutorService des = new DefaultExecutorService(cache(0, EXECUTION_CACHE));
      CompletableFuture<Integer> future = des.submit(new SimpleCallable());
      assertEquals(Integer.valueOf(1), future.get());
   }

   public void testExecDistExec() throws Exception {
      Policy.setPolicy(new SurefireTestingPolicy());
      System.setSecurityManager(new SecurityManager());
      try {
         Subject.doAs(EXEC, (PrivilegedExceptionAction<Void>) () -> {
            distExecTest();
            return null;
         });
      } finally {
         System.setSecurityManager(null);
         Policy.setPolicy(null);
      }
   }

   @Test(expectedExceptions=SecurityException.class)
   public void testNoExecDistExec() throws Exception {
      Policy.setPolicy(new SurefireTestingPolicy());
      try {
         System.setSecurityManager(new SecurityManager());
         Subject.doAs(NOEXEC, (PrivilegedExceptionAction<Void>) () -> {
            distExecTest();
            return null;
         });
      } finally {
         System.setSecurityManager(null);
         Policy.setPolicy(null);
      }
   }


   static class SimpleCallable implements Callable<Integer>, Serializable {

      /** The serialVersionUID */
      private static final long serialVersionUID = -8589149500259272402L;

      public SimpleCallable() {
      }

      @Override
      public Integer call() throws Exception {
         return 1;
      }
   }
}
