package org.infinispan.scripting;

import static org.testng.AssertJUnit.assertEquals;

import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.ExecutionException;

import javax.security.auth.Subject;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.AuthorizationConfigurationBuilder;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalAuthorizationConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.security.AuthorizationPermission;
import org.infinispan.security.Security;
import org.infinispan.security.impl.IdentityRoleMapper;
import org.infinispan.tasks.TaskContext;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.testng.annotations.Test;

@Test(groups = "functional", testName = "scripting.SecureScriptingTest")
public class SecureScriptingTest extends AbstractScriptingTest {

   static final Subject ADMIN = TestingUtil.makeSubject("admin", ScriptingManager.SCRIPT_MANAGER_ROLE);
   static final Subject RUNNER = TestingUtil.makeSubject("runner", "runner");
   static final Subject PHEIDIPPIDES = TestingUtil.makeSubject("pheidippides", "pheidippides");
   static final Subject ACHILLES = TestingUtil.makeSubject("achilles", "achilles");
   static final String SECURE_CACHE_NAME = "secured-script-exec";

   @Override
   protected EmbeddedCacheManager createCacheManager() throws Exception {
      GlobalConfigurationBuilder global = new GlobalConfigurationBuilder();
      GlobalAuthorizationConfigurationBuilder globalRoles = global.security().authorization().enable().principalRoleMapper(new IdentityRoleMapper());
      ConfigurationBuilder config = TestCacheManagerFactory.getDefaultCacheConfiguration(true);
      AuthorizationConfigurationBuilder authConfig = config.security().authorization().enable();

      globalRoles
         .role("achilles")
            .permission(AuthorizationPermission.READ)
            .permission(AuthorizationPermission.WRITE)
         .role("runner")
            .permission(AuthorizationPermission.EXEC)
            .permission(AuthorizationPermission.READ)
            .permission(AuthorizationPermission.WRITE)
         .role("pheidippides")
            .permission(AuthorizationPermission.EXEC)
            .permission(AuthorizationPermission.READ)
            .permission(AuthorizationPermission.WRITE)
         .role("admin")
            .permission(AuthorizationPermission.ALL);
      authConfig.role("runner").role("pheidippides").role("admin");
      EmbeddedCacheManager cm = TestCacheManagerFactory.createCacheManager(global, config);
      Security.doAs(ADMIN, (PrivilegedExceptionAction<Void>) () -> {
         cm.defineConfiguration(ScriptingTest.CACHE_NAME, cm.getDefaultCacheConfiguration());
         cm.getCache(ScriptingTest.CACHE_NAME);
         cm.defineConfiguration(SecureScriptingTest.SECURE_CACHE_NAME, cm.getDefaultCacheConfiguration());
         cm.getCache(SecureScriptingTest.SECURE_CACHE_NAME);
         cm.defineConfiguration("nonSecuredCache", TestCacheManagerFactory.getDefaultCacheConfiguration(true).build());
         return null;
      });

      return cm;
   }

   @Override
   protected String[] getScripts() {
      return new String[] { "test.js", "testRole.js", "testRoleWithCache.js" };
   }

   @Override
   protected void setup() throws Exception {
      Security.doAs(ADMIN, (PrivilegedExceptionAction<Void>) () -> {
         SecureScriptingTest.super.setup();
         return null;
      });
   }

   @Override
   protected void teardown() {
      Security.doAs(ADMIN, (PrivilegedAction<Void>) () -> {
         SecureScriptingTest.super.teardown();
         return null;
      });
   }

   @Override
   protected void clearContent() {
      Security.doAs(ADMIN, (PrivilegedAction<Void>) () -> {
         cacheManager.getCache().clear();
         return null;
      });
   }

   @Test(expectedExceptions = SecurityException.class)
   public void testSimpleScript() throws Exception {
      String result = (String) scriptingManager.runScript("test.js", new TaskContext().addParameter("a", "a")).get();
      assertEquals("a", result);
   }

   public void testSimpleScriptWithEXECPermissions() throws Exception {
      String result = Security.doAs(RUNNER, (PrivilegedExceptionAction<String>) () -> (String) scriptingManager.runScript("test.js", new TaskContext().addParameter("a", "a")).get());
      assertEquals("a", result);
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testSimpleScriptWithEXECPermissionsWrongRole() throws Exception {
      String result = Security.doAs(RUNNER, (PrivilegedExceptionAction<String>) () -> (String) scriptingManager.runScript("testRole.js", new TaskContext().addParameter("a", "a")).get());
      assertEquals("a", result);
   }

   public void testSimpleScriptWithEXECPermissionsRightRole() throws Exception {
      String result = Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<String>) () -> (String) scriptingManager.runScript("testRole.js", new TaskContext().addParameter("a", "a")).get());
      assertEquals("a", result);
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testSimpleScriptWithoutEXEC() throws Exception {
      Security.doAs(ACHILLES, (PrivilegedExceptionAction<String>) () -> (String) scriptingManager.runScript("testRole.js", new TaskContext().addParameter("a", "a")).get());
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testUploadScriptWithEXECNotManager() throws Exception {
      Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<Void>) () -> {
         scriptingManager.addScript("my_script", "1+1");
         return null;
      });
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testUploadScriptWithoutEXECNotManager() throws Exception {
      Security.doAs(ACHILLES, (PrivilegedExceptionAction<Void>) () -> {
         scriptingManager.addScript("my_script", "1+1");
         return null;
      });
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testRemoveScriptWithEXECNotManager() throws Exception {
      Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<Void>) () -> {
         scriptingManager.removeScript("test.js");
         return null;
      });
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testUploadScriptDirectlyWithEXECNotManager() throws Exception {
      Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<Void>) () -> {
         cacheManager.getCache(ScriptingManager.SCRIPT_CACHE).put("my_script", "1+1");
         return null;
      });
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testRemoveScriptDirectlyWithEXECNotManager() throws Exception {
      Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<Void>) () -> {
         cacheManager.getCache(ScriptingManager.SCRIPT_CACHE).remove("test.js");
         return null;
      });
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testClearScriptDirectlyWithEXECNotManager() throws Exception {
      Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<Void>) () -> {
         cacheManager.getCache(ScriptingManager.SCRIPT_CACHE).clear();
         return null;
      });
   }

   public void testScriptOnNonSecuredCache() throws ExecutionException, InterruptedException, PrivilegedActionException {
      Cache<String, String> nonSecCache = cache("nonSecuredCache");
      nonSecCache.put("a", "value");
      assertEquals("value", nonSecCache.get("a"));

      String result = Security.doAs(PHEIDIPPIDES, (PrivilegedExceptionAction<String>) () -> (String) scriptingManager.runScript("testRoleWithCache.js", new TaskContext().addParameter("a", "a").cache(nonSecCache)).get());
      assertEquals("a", result);
      assertEquals("a", nonSecCache.get("a"));
   }

   @Test(expectedExceptions = PrivilegedActionException.class)
   public void testScriptOnNonSecuredCacheWrongRole() throws ExecutionException, InterruptedException, PrivilegedActionException {
      Security.doAs(RUNNER, (PrivilegedExceptionAction<String>) () -> (String) scriptingManager.runScript("testRoleWithCache.js", new TaskContext().addParameter("a", "a").cache(cache("nonSecuredCache"))).get());
   }

}
