You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2016/12/06 10:19:57 UTC

tomee git commit: TOMEE-1982 @RunAs for nested calls only

Repository: tomee
Updated Branches:
  refs/heads/master 61c6522b3 -> 0711ccbc6


TOMEE-1982 @RunAs for nested calls only


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/0711ccbc
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/0711ccbc
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/0711ccbc

Branch: refs/heads/master
Commit: 0711ccbc6cfc680534c9d76d6a915830283b28c7
Parents: 61c6522
Author: rmannibucau <rm...@apache.org>
Authored: Tue Dec 6 11:19:49 2016 +0100
Committer: rmannibucau <rm...@apache.org>
Committed: Tue Dec 6 11:19:49 2016 +0100

----------------------------------------------------------------------
 .../core/security/AbstractSecurityService.java  | 35 ++++-----
 .../core/singleton/SingletonContainer.java      | 16 ++++
 .../core/stateful/StatefulContainer.java        | 50 +++++++++++-
 .../core/stateless/StatelessContainer.java      | 16 ++++
 .../org/apache/openejb/threads/task/CUTask.java | 14 +++-
 .../core/security/RoleAllowedAndRunAsTest.java  | 80 ++++++++++++++++++++
 .../apache/openejb/core/security/RunAsTest.java | 23 ++++--
 .../threads/SecurityPropagationTest.java        | 31 +++++---
 8 files changed, 223 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java b/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
index 1c6e9dd..17b2585 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/security/AbstractSecurityService.java
@@ -151,34 +151,31 @@ public abstract class AbstractSecurityService implements DestroyableResource, Se
         final String moduleID = newContext.getBeanContext().getModuleID();
         PolicyContext.setContextID(moduleID);
 
-        Subject runAsSubject = getRunAsSubject(newContext.getBeanContext());
-        if (oldContext != null && runAsSubject == null) {
-            runAsSubject = getRunAsSubject(oldContext.getBeanContext());
-        }
-
         final ProvidedSecurityContext providedSecurityContext = newContext.get(ProvidedSecurityContext.class);
         SecurityContext securityContext = oldContext != null ? oldContext.get(SecurityContext.class) :
             (providedSecurityContext != null ? providedSecurityContext.context : null);
-        if (providedSecurityContext == null) {
-            if (runAsSubject != null) {
-
-                securityContext = new SecurityContext(runAsSubject);
-
-            } else if (securityContext == null) {
-
-                final Identity identity = clientIdentity.get();
-                if (identity != null) {
-                    securityContext = new SecurityContext(identity.subject);
-                } else {
-                    securityContext = defaultContext;
-                }
+        if (providedSecurityContext == null && (securityContext == null || securityContext == defaultContext)) {
+            final Identity identity = clientIdentity.get();
+            if (identity != null) {
+                securityContext = new SecurityContext(identity.subject);
+            } else {
+                securityContext = defaultContext;
             }
         }
 
         newContext.set(SecurityContext.class, securityContext);
     }
 
-    protected Subject getRunAsSubject(final BeanContext callingBeanContext) {
+    public UUID overrideWithRunAsContext(final ThreadContext ctx, final BeanContext newContext, final BeanContext oldContext) {
+        Subject runAsSubject = getRunAsSubject(newContext);
+        if (oldContext != null && runAsSubject == null) {
+            runAsSubject = getRunAsSubject(oldContext);
+        }
+        ctx.set(SecurityContext.class, new SecurityContext(runAsSubject));
+        return disassociate();
+    }
+
+    public Subject getRunAsSubject(final BeanContext callingBeanContext) {
         if (callingBeanContext == null) {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/main/java/org/apache/openejb/core/singleton/SingletonContainer.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/singleton/SingletonContainer.java b/container/openejb-core/src/main/java/org/apache/openejb/core/singleton/SingletonContainer.java
index b6551b0..87775ac 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/singleton/SingletonContainer.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/singleton/SingletonContainer.java
@@ -29,6 +29,7 @@ import org.apache.openejb.core.Operation;
 import org.apache.openejb.core.ThreadContext;
 import org.apache.openejb.core.interceptor.InterceptorData;
 import org.apache.openejb.core.interceptor.InterceptorStack;
+import org.apache.openejb.core.security.AbstractSecurityService;
 import org.apache.openejb.core.timer.EjbTimerService;
 import org.apache.openejb.core.transaction.TransactionPolicy;
 import org.apache.openejb.core.webservices.AddressingSupport;
@@ -44,6 +45,7 @@ import javax.ejb.EJBLocalHome;
 import javax.ejb.EJBLocalObject;
 import javax.ejb.EJBObject;
 import javax.interceptor.AroundInvoke;
+import javax.security.auth.login.LoginException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -178,7 +180,14 @@ public class SingletonContainer implements RpcContainer {
         final ThreadContext callContext = new ThreadContext(beanContext, primKey);
         final ThreadContext oldCallContext = ThreadContext.enter(callContext);
         final CurrentCreationalContext currentCreationalContext = beanContext.get(CurrentCreationalContext.class);
+        Object runAs = null;
         try {
+            if (oldCallContext != null) {
+                final BeanContext oldBc = oldCallContext.getBeanContext();
+                if (oldBc.getRunAsUser() != null || oldBc.getRunAs() != null) {
+                    runAs = AbstractSecurityService.class.cast(securityService).overrideWithRunAsContext(callContext, beanContext, oldBc);
+                }
+            }
 
             final boolean authorized = type == InterfaceType.TIMEOUT || getSecurityService().isCallerAuthorized(callMethod, type);
 
@@ -212,6 +221,13 @@ public class SingletonContainer implements RpcContainer {
             return _invoke(callMethod, runMethod, args, instance, callContext, type);
 
         } finally {
+            if (runAs != null) {
+                try {
+                    securityService.associate(runAs);
+                } catch (final LoginException e) {
+                    // no-op
+                }
+            }
             ThreadContext.exit(oldCallContext);
             if (currentCreationalContext != null) {
                 currentCreationalContext.remove();

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java b/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java
index e1f58f8..9eca1d8 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/stateful/StatefulContainer.java
@@ -35,6 +35,7 @@ import org.apache.openejb.core.Operation;
 import org.apache.openejb.core.ThreadContext;
 import org.apache.openejb.core.interceptor.InterceptorData;
 import org.apache.openejb.core.interceptor.InterceptorStack;
+import org.apache.openejb.core.security.AbstractSecurityService;
 import org.apache.openejb.core.stateful.Cache.CacheFilter;
 import org.apache.openejb.core.stateful.Cache.CacheListener;
 import org.apache.openejb.core.transaction.BeanTransactionPolicy;
@@ -74,6 +75,7 @@ import javax.naming.NamingException;
 import javax.persistence.EntityManager;
 import javax.persistence.EntityManagerFactory;
 import javax.persistence.SynchronizationType;
+import javax.security.auth.login.LoginException;
 import javax.transaction.Transaction;
 import java.io.Serializable;
 import java.lang.reflect.Method;
@@ -392,7 +394,15 @@ public class StatefulContainer implements RpcContainer {
 
         final ThreadContext createContext = new ThreadContext(beanContext, primaryKey);
         final ThreadContext oldCallContext = ThreadContext.enter(createContext);
+        Object runAs = null;
         try {
+            if (oldCallContext != null) {
+                final BeanContext oldBc = oldCallContext.getBeanContext();
+                if (oldBc.getRunAsUser() != null || oldBc.getRunAs() != null) {
+                    runAs = AbstractSecurityService.class.cast(securityService).overrideWithRunAsContext(createContext, beanContext, oldBc);
+                }
+            }
+
             // Security check
             checkAuthorization(callMethod, interfaceType);
 
@@ -471,6 +481,13 @@ public class StatefulContainer implements RpcContainer {
 
             return new ProxyInfo(beanContext, primaryKey);
         } finally {
+            if (runAs != null) {
+                try {
+                    securityService.associate(runAs);
+                } catch (final LoginException e) {
+                    // no-op
+                }
+            }
             ThreadContext.exit(oldCallContext);
         }
     }
@@ -496,7 +513,14 @@ public class StatefulContainer implements RpcContainer {
 
         final ThreadContext callContext = new ThreadContext(beanContext, primKey);
         final ThreadContext oldCallContext = ThreadContext.enter(callContext);
+        Object runAs = null;
         try {
+            if (oldCallContext != null) {
+                final BeanContext oldBc = oldCallContext.getBeanContext();
+                if (oldBc.getRunAsUser() != null || oldBc.getRunAs() != null) {
+                    runAs = AbstractSecurityService.class.cast(securityService).overrideWithRunAsContext(callContext, beanContext, oldBc);
+                }
+            }
             // Security check
             if (!internalRemove) {
                 checkAuthorization(callMethod, interfaceType);
@@ -592,6 +616,13 @@ public class StatefulContainer implements RpcContainer {
                     }
                 }
             } finally {
+                if (runAs != null) {
+                    try {
+                        securityService.associate(runAs);
+                    } catch (final LoginException e) {
+                        // no-op
+                    }
+                }
                 if (!retain) {
                     try {
                         callContext.setCurrentOperation(Operation.PRE_DESTROY);
@@ -642,7 +673,15 @@ public class StatefulContainer implements RpcContainer {
         final ThreadContext callContext = new ThreadContext(beanContext, primKey);
         final ThreadContext oldCallContext = ThreadContext.enter(callContext);
         final CurrentCreationalContext currentCreationalContext = beanContext.get(CurrentCreationalContext.class);
+        Object runAs = null;
         try {
+            if (oldCallContext != null) {
+                final BeanContext oldBc = oldCallContext.getBeanContext();
+                if (oldBc.getRunAsUser() != null || oldBc.getRunAs() != null) {
+                    runAs = AbstractSecurityService.class.cast(securityService).overrideWithRunAsContext(callContext, beanContext, oldBc);
+                }
+            }
+
             // Security check
             checkAuthorization(callMethod, interfaceType);
 
@@ -699,6 +738,13 @@ public class StatefulContainer implements RpcContainer {
             }
             return returnValue;
         } finally {
+            if (runAs != null) {
+                try {
+                    securityService.associate(runAs);
+                } catch (final LoginException e) {
+                    // no-op
+                }
+            }
             ThreadContext.exit(oldCallContext);
             if (currentCreationalContext != null) {
                 currentCreationalContext.remove();
@@ -732,7 +778,7 @@ public class StatefulContainer implements RpcContainer {
                     throw new InvalidateReferenceException(new NoSuchObjectException("Not Found"));
                 }
 
-                // remember instance until it is returned to the cache                
+                // remember instance until it is returned to the cache
                 checkedOutInstances.put(primaryKey, instance);
             }
         }
@@ -749,7 +795,7 @@ public class StatefulContainer implements RpcContainer {
             // concurrent calls are not allowed, lock only once
             lockAcquired = currLock.tryLock();
         } else {
-            // try to get a lock within the specified period. 
+            // try to get a lock within the specified period.
             try {
                 lockAcquired = currLock.tryLock(accessTimeout.getTime(), accessTimeout.getUnit());
             } catch (final InterruptedException e) {

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/main/java/org/apache/openejb/core/stateless/StatelessContainer.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/stateless/StatelessContainer.java b/container/openejb-core/src/main/java/org/apache/openejb/core/stateless/StatelessContainer.java
index 11f43e0..ea44ea4 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/stateless/StatelessContainer.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/stateless/StatelessContainer.java
@@ -30,6 +30,7 @@ import org.apache.openejb.core.Operation;
 import org.apache.openejb.core.ThreadContext;
 import org.apache.openejb.core.interceptor.InterceptorData;
 import org.apache.openejb.core.interceptor.InterceptorStack;
+import org.apache.openejb.core.security.AbstractSecurityService;
 import org.apache.openejb.core.timer.EjbTimerService;
 import org.apache.openejb.core.transaction.TransactionPolicy;
 import org.apache.openejb.core.webservices.AddressingSupport;
@@ -51,6 +52,7 @@ import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executors;
 import javax.interceptor.AroundInvoke;
+import javax.security.auth.login.LoginException;
 
 import static org.apache.openejb.core.transaction.EjbTransactionUtil.afterInvoke;
 import static org.apache.openejb.core.transaction.EjbTransactionUtil.createTransactionPolicy;
@@ -171,7 +173,14 @@ public class StatelessContainer implements org.apache.openejb.RpcContainer, Dest
         Instance bean = null;
         final CurrentCreationalContext currentCreationalContext = beanContext.get(CurrentCreationalContext.class);
 
+        Object runAs = null;
         try {
+            if (oldCallContext != null) {
+                final BeanContext oldBc = oldCallContext.getBeanContext();
+                if (oldBc.getRunAsUser() != null || oldBc.getRunAs() != null) {
+                    runAs = AbstractSecurityService.class.cast(securityService).overrideWithRunAsContext(callContext, beanContext, oldBc);
+                }
+            }
 
             //Check auth before overriding context
             final boolean authorized = type == InterfaceType.TIMEOUT || this.securityService.isCallerAuthorized(callMethod, type);
@@ -202,6 +211,13 @@ public class StatelessContainer implements org.apache.openejb.RpcContainer, Dest
             }
             return _invoke(callMethod, runMethod, args, bean, callContext, type);
         } finally {
+            if (runAs != null) {
+                try {
+                    securityService.associate(runAs);
+                } catch (final LoginException e) {
+                    // no-op
+                }
+            }
             if (bean != null) {
                 if (callContext.isDiscardInstance()) {
                     this.instanceManager.discardInstance(callContext, bean);

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/main/java/org/apache/openejb/threads/task/CUTask.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/threads/task/CUTask.java b/container/openejb-core/src/main/java/org/apache/openejb/threads/task/CUTask.java
index e3bef03..e2270a6 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/threads/task/CUTask.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/threads/task/CUTask.java
@@ -32,6 +32,7 @@ import java.util.Collection;
 import java.util.concurrent.Callable;
 
 public abstract class CUTask<T> extends ManagedTaskListenerTask implements Comparable<Object> {
+    // TODO: get rid of it as a static thing, make it owned by the executor probably
     private static final SecurityService SECURITY_SERVICE = SystemInstance.get().getComponent(SecurityService.class);
 
     // only updated in container startup phase, no concurrency possible, don't use it at runtime!
@@ -61,9 +62,16 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa
             associate = false;
         }
         final ThreadContext threadContext = ThreadContext.getThreadContext();
-        initialContext = new Context(
-            associate, stateTmp, threadContext == null ? null : threadContext.get(AbstractSecurityService.SecurityContext.class),
-            threadContext, Thread.currentThread().getContextClassLoader(), null);
+        final AbstractSecurityService.SecurityContext sc = threadContext == null ? null : threadContext.get(AbstractSecurityService.SecurityContext.class);
+        if (threadContext != null && threadContext.getBeanContext() != null &&
+                (threadContext.getBeanContext().getRunAs() != null || threadContext.getBeanContext().getRunAsUser() != null)) {
+            initialContext = new Context(
+                    associate, stateTmp,
+                    new AbstractSecurityService.SecurityContext(AbstractSecurityService.class.cast(SECURITY_SERVICE).getRunAsSubject(threadContext.getBeanContext())),
+                    threadContext, Thread.currentThread().getContextClassLoader(), null);
+        } else {
+            initialContext = new Context(associate, stateTmp, sc, threadContext, Thread.currentThread().getContextClassLoader(), null);
+        }
         if (CONTAINER_LISTENERS.length > 0) {
             containerListenerStates = new Object[CONTAINER_LISTENERS.length];
             for (int i = 0; i < CONTAINER_LISTENERS.length; i++) {

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/test/java/org/apache/openejb/core/security/RoleAllowedAndRunAsTest.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/core/security/RoleAllowedAndRunAsTest.java b/container/openejb-core/src/test/java/org/apache/openejb/core/security/RoleAllowedAndRunAsTest.java
new file mode 100644
index 0000000..c131c8d
--- /dev/null
+++ b/container/openejb-core/src/test/java/org/apache/openejb/core/security/RoleAllowedAndRunAsTest.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.core.security;
+
+import org.apache.openejb.junit.ApplicationComposer;
+import org.apache.openejb.loader.SystemInstance;
+import org.apache.openejb.spi.SecurityService;
+import org.apache.openejb.testing.Classes;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.annotation.Resource;
+import javax.annotation.security.RolesAllowed;
+import javax.annotation.security.RunAs;
+import javax.ejb.EJB;
+import javax.ejb.EJBContext;
+import javax.ejb.Singleton;
+import javax.security.auth.login.LoginException;
+
+import static org.junit.Assert.assertEquals;
+
+@Classes(innerClassesAsBean = true)
+@RunWith(ApplicationComposer.class)
+public class RoleAllowedAndRunAsTest {
+    @EJB
+    private DefaultRoles bean;
+
+    @Test
+    public void run() throws LoginException {
+        final SecurityService securityService = SystemInstance.get().getComponent(SecurityService.class);
+        final Object id = securityService.login("jonathan", "secret");
+        securityService.associate(id);
+        try {
+            assertEquals("jonathan > role1", bean.stack());
+        } finally {
+            securityService.disassociate();
+            securityService.logout(id);
+        }
+    }
+
+    @Singleton
+    public static class Identity {
+        @Resource
+        private EJBContext context;
+
+        @RolesAllowed("role1")
+        public String name() {
+            return context.getCallerPrincipal().getName();
+        }
+    }
+
+    @Singleton
+    @RunAs("role1")
+    @RolesAllowed("committer")
+    public static class DefaultRoles {
+        @Resource
+        private EJBContext context;
+
+        @EJB
+        private Identity identity;
+
+        public String stack() {
+            return context.getCallerPrincipal().getName() + " > " + identity.name();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/test/java/org/apache/openejb/core/security/RunAsTest.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/core/security/RunAsTest.java b/container/openejb-core/src/test/java/org/apache/openejb/core/security/RunAsTest.java
index fffa829..db4ee2a 100644
--- a/container/openejb-core/src/test/java/org/apache/openejb/core/security/RunAsTest.java
+++ b/container/openejb-core/src/test/java/org/apache/openejb/core/security/RunAsTest.java
@@ -17,7 +17,7 @@
 package org.apache.openejb.core.security;
 
 import org.apache.openejb.junit.ApplicationComposer;
-import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.Classes;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -30,13 +30,9 @@ import javax.ejb.Singleton;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+@Classes(innerClassesAsBean = true)
 @RunWith(ApplicationComposer.class)
 public class RunAsTest {
-    @Module
-    public Class<?>[] beans() {
-        return new Class<?>[]{MyRunAsBean.class};
-    }
-
     @EJB
     private MyRunAsBean bean;
 
@@ -49,6 +45,21 @@ public class RunAsTest {
     @RunAs("foo")
     @Singleton
     public static class MyRunAsBean {
+        @EJB
+        private Delegate delegate;
+
+        public String principal() {
+            return delegate.principal();
+        }
+
+        public boolean isInRole() {
+            return delegate.isInRole();
+        }
+    }
+
+    @RunAs("foo")
+    @Singleton
+    public static class Delegate {
         @Resource
         private SessionContext ctx;
 

http://git-wip-us.apache.org/repos/asf/tomee/blob/0711ccbc/container/openejb-core/src/test/java/org/apache/openejb/threads/SecurityPropagationTest.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/test/java/org/apache/openejb/threads/SecurityPropagationTest.java b/container/openejb-core/src/test/java/org/apache/openejb/threads/SecurityPropagationTest.java
index b96797b..f2fcafe 100644
--- a/container/openejb-core/src/test/java/org/apache/openejb/threads/SecurityPropagationTest.java
+++ b/container/openejb-core/src/test/java/org/apache/openejb/threads/SecurityPropagationTest.java
@@ -16,10 +16,8 @@
  */
 package org.apache.openejb.threads;
 
-import org.apache.openejb.jee.EnterpriseBean;
-import org.apache.openejb.jee.StatelessBean;
 import org.apache.openejb.junit.ApplicationComposer;
-import org.apache.openejb.testing.Module;
+import org.apache.openejb.testing.Classes;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -33,28 +31,35 @@ import javax.enterprise.concurrent.ManagedExecutorService;
 import javax.naming.InitialContext;
 import javax.naming.NamingException;
 import java.security.Principal;
+import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
+@Classes(innerClassesAsBean = true)
 @RunWith(ApplicationComposer.class)
 public class SecurityPropagationTest {
-    @Module
-    public EnterpriseBean bean() {
-        return new StatelessBean(ExecutorBean.class).localBean();
-    }
-
     @EJB
     private ExecutorBean bean;
 
     @Test
     public void checkItIsTrue() throws Exception {
-        bean.submit(new RunnableTest()).get();
+        assertEquals("tomee", bean.submit(new RunnableTest()).get());
     }
 
     @Stateless
     @RunAs("tomee")
     public static class ExecutorBean {
+        @EJB
+        private Delegate delegate;
+
+        public Future<?> submit(final RunnableTest task) throws NamingException {
+            return delegate.submit(task);
+        }
+    }
+
+    @Stateless
+    public static class Delegate {
         @Resource
         private ManagedExecutorService executorService;
 
@@ -68,7 +73,7 @@ public class SecurityPropagationTest {
         }
     }
 
-    public static class RunnableTest implements Runnable {
+    public static class RunnableTest implements Callable<String> {
         private Principal expectedPrincipal;
 
         public void setExpectedPrincipal(final Principal expectedPrincipal) {
@@ -76,7 +81,7 @@ public class SecurityPropagationTest {
         }
 
         @Override
-        public void run() {
+        public String call() throws Exception {
             try {
                 Thread.sleep(200);
             } catch (final InterruptedException e) {
@@ -106,6 +111,8 @@ public class SecurityPropagationTest {
                     throw new IllegalStateException("the caller principal " + callerPrincipal + " is not the expected " + expectedPrincipal);
                 }
             }
+
+            return callerPrincipal.getName();
         }
 
     }