Search code examples
javamultithreadingjakarta-eeconcurrencycdi

How to avoid ExecutorService from overridding Security Principal of a Runnable


When the first runnable is submitted is an inject ExecutorService, the security Principal is correctly set for that runnable. Each subsequently submitted runnable is given the security principal of the original user instead of keeping the current runnable. My development machine is running Wildfly 8.2 .

I am creating a reporting system for asynchronous processing. I created a service that checks which user created the task and ensures that only that user can start or complete the task. The code for the service is below.

@Stateless
public class ReportingService {
   //EE injection security context
   @Resource
   SessionContext context;
   //CDI security Principal
   @Inject
   Principal principal;

   //this method handles getting the username for EE injection or CDI
   private String getCurrentUser() {
       if (context != null) {
           return context.getCallerPrincipal().getName();
       }
       if (principal != null) {
           return principal.getName();
       }
       return null;
   }

   @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   @Transactional
   public void registerTask(String taskId) {
       //Create task
       //set task.submittedBy = getCurrentUser()
       //persist task
       //code has been omitted since it is working
   }

   private void validateCurrentUserRegisteredJob(String taskId) {
       String user = //get user that created task with id = id from DB
       String currentUser = getCurrentUser();
       if (!user.equals(currentUser)) {
           throw new EJBAccesException("Current user "+currentUser+" did not register task");
       }
   }

   @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   @Transactional
   public void startTask(String taskId) {
       validateCurrentUserRegisteredJob(taskid);
       //retrieve task entity, set start time to now, and merge
   }
   ...
}

Below is my Runnable code

public TaskRunner() implements Runnable {
    //CDI principal
    @Inject
    Principal principal;
    @Inject
    ReportingService rs;

    private taskId;

    public void setTaskId() {...}

    public void run() {
       log.debug("Inside Runner Current User: "+principal.getName());
       rs.startTask(taskId);
       ....
    }
}

The following is the code from the Stateless Bean that is called by a REST endpoint that kicks off the process

@Stateless
public ProjectService() {
    @Inject
    Instance<TaskRunner> taskRunner;
    @Inject
    ReportingService reportingService;

    //ExecutorService that is create from Adam Bien's porcupine project
    @Inject
    @Dedicated
    ExecutorService es;

    //method that is called by rest enpoint to kick off 
    public void performAsynchAction(List<String> taskIds, ...rest of args...) {
        taskIds.stream().forEach(t -> {
            //registers task with user that made REST call
            reportingService.registerTask(t);
            TaskRunner runner = taskRunner.get();
            runner.setTaskId(t);
            log.debug("Created runner. Principal: "+runner.principal.getName());
            es.submit(runner);
        });
    }
}

Here is the chart of the call flow

REST -> ProjectService.performAsynchAction(...)
         -> reportingService.registerTask(...)
         -> create CDI injected Runnable
         -> submit runner to executor service
             -> ExecutorService calls Runner.run()
                 -> rs.startTask(taskId)

I call the Rest end point as user1 for the first time and register tasks: 1-2. They all work as expected and I get the following output in my log.

Created runner. Principal: user1
Created runner. Principal: user1
Inside Runner Current User: user1
Inside Runner Current User: user1

The next time I make the same REST call as user2 and I get the following output in the log

Created runner. Principal: user2
Inside Runner Current User: user1
EJBAccessException Current user user1 did not register task

It appears that the Security Principal of the Runnable is correctly set the first time a Runnable is submitted to the ExecutorService. But for each subsequent Runneable that is submitted to the ExecutorService uses the security Principal of the first submitted runnable. Is this a bug or the intended behavior? Does anyone know of a potential work around?

EDIT: I figure out that the porcupine project I was using to create the ExecutorService was not being managed by the container. Once I switched to a ManagedExecutorService, the SessionContext was being properly propagated.

@Resource(lookup = "java:jboss/ee/concurrency/executor/customExecutor")
private ManagedExecutorService es;

Solution

  • I figured out the issue. I looked into the porcupine code and found out that the ExecutorService was not being managed by the Container. I created a ManagerExecutorService and the SessionContext was then being properly propogated.

    @Resource(lookup = "java:jboss/ee/concurrency/executor/customExecutor")
    private ManagedExecutorService es_;