Repository: tomee Updated Branches: refs/heads/master 1b960482e -> ed6331f1d
TOMEE-1695 keep track of logged user for tasks in managed executor services Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/ed6331f1 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/ed6331f1 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/ed6331f1 Branch: refs/heads/master Commit: ed6331f1d5a6c6fda8e071634498688760156a96 Parents: 1b96048 Author: Romain Manni-Bucau <[email protected]> Authored: Wed Jan 20 15:11:07 2016 +0100 Committer: Romain Manni-Bucau <[email protected]> Committed: Wed Jan 20 15:11:07 2016 +0100 ---------------------------------------------------------------------- ...edExecutorServiceGetPrincipalInTaskTest.java | 60 ++++++++++++++++++++ .../arquillian/managed/ConcurrencyServlet.java | 59 +++++++++++++++++++ .../apache/openejb/arquillian/managed/User.java | 33 +++++++++++ .../src/test/resources/managed/tomcat-users.xml | 23 ++++++++ .../org/apache/openejb/threads/task/CUTask.java | 35 +++++++++++- .../org/apache/tomee/catalina/TomEERealm.java | 25 +++++++- 6 files changed, 231 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/ed6331f1/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/ManagedExecutorServiceGetPrincipalInTaskTest.java ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/ManagedExecutorServiceGetPrincipalInTaskTest.java b/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/ManagedExecutorServiceGetPrincipalInTaskTest.java new file mode 100644 index 0000000..789aeda --- /dev/null +++ b/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/ManagedExecutorServiceGetPrincipalInTaskTest.java @@ -0,0 +1,60 @@ +/** + * 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.arquillian; + +import org.apache.catalina.realm.MemoryRealm; +import org.apache.openejb.arquillian.common.IO; +import org.apache.openejb.arquillian.managed.ConcurrencyServlet; +import org.apache.openejb.arquillian.managed.User; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import static org.junit.Assert.assertEquals; + +@RunWith(Arquillian.class) +public class ManagedExecutorServiceGetPrincipalInTaskTest { + @Deployment(testable = false) + public static Archive<?> app() { + return ShrinkWrap.create(WebArchive.class, "mp.war") + .addClasses(ConcurrencyServlet.class, User.class) + .addAsManifestResource(new StringAsset( + "<Context>" + + " <Realm className=\"" + MemoryRealm.class.getName() + + "\" pathname=\"" + + new File("src/test/resources/managed/tomcat-users.xml").getAbsolutePath() + "\" />" + + "</Context>"), "context.xml"); + } + + @ArquillianResource + private URL url; + + @Test + public void run() throws IOException { + assertEquals("test", IO.slurp(new URL(url, "async")).trim()); + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/ed6331f1/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/ConcurrencyServlet.java ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/ConcurrencyServlet.java b/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/ConcurrencyServlet.java new file mode 100644 index 0000000..48d38f0 --- /dev/null +++ b/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/ConcurrencyServlet.java @@ -0,0 +1,59 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.arquillian.managed; + +import javax.annotation.Resource; +import javax.ejb.EJB; +import javax.enterprise.concurrent.ManagedExecutorService; +import javax.servlet.AsyncContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet(asyncSupported = true, value = "/async") +public class ConcurrencyServlet extends HttpServlet { + @EJB + private User user; + + @Resource + private ManagedExecutorService execService; + + @Override + protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + final AsyncContext asyncCtx = req.startAsync(); + execService.execute(new Runnable() { + @Override + public void run() { + try { + req.login("test", "secret"); + resp.getWriter().println(user.getUser()); + } catch (final Exception e) { + try { + e.printStackTrace(resp.getWriter()); + } catch (final IOException e1) { + throw new IllegalStateException(e); + } + } finally { + asyncCtx.complete(); + } + } + }); + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/ed6331f1/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/User.java ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/User.java b/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/User.java new file mode 100644 index 0000000..88fb1d2 --- /dev/null +++ b/arquillian/arquillian-tomee-remote/src/test/java/org/apache/openejb/arquillian/managed/User.java @@ -0,0 +1,33 @@ +/** + * 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.arquillian.managed; + +import javax.ejb.SessionContext; +import javax.ejb.Stateless; +import javax.naming.InitialContext; + +@Stateless +public class User { + public String getUser() { + try { + final SessionContext ctx = (SessionContext) new InitialContext().lookup("java:comp/EJBContext"); + return ctx.getCallerPrincipal().getName(); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/ed6331f1/arquillian/arquillian-tomee-remote/src/test/resources/managed/tomcat-users.xml ---------------------------------------------------------------------- diff --git a/arquillian/arquillian-tomee-remote/src/test/resources/managed/tomcat-users.xml b/arquillian/arquillian-tomee-remote/src/test/resources/managed/tomcat-users.xml new file mode 100644 index 0000000..7a9ee2f --- /dev/null +++ b/arquillian/arquillian-tomee-remote/src/test/resources/managed/tomcat-users.xml @@ -0,0 +1,23 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- + 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. +--> +<tomcat-users xmlns="http://tomcat.apache.org/xml" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" + version="1.0"> + <user username="test" password="secret" /> +</tomcat-users> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/ed6331f1/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 146b52e..dbcd2f0 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 @@ -24,6 +24,8 @@ import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.SecurityService; import javax.security.auth.login.LoginException; +import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.Callable; public abstract class CUTask<T> extends ManagedTaskListenerTask implements Comparable<Object> { @@ -45,7 +47,7 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa final ThreadContext threadContext = ThreadContext.getThreadContext(); initialContext = new Context( associate, stateTmp, threadContext == null ? null : threadContext.get(AbstractSecurityService.SecurityContext.class), - threadContext, Thread.currentThread().getContextClassLoader()); + threadContext, Thread.currentThread().getContextClassLoader(), null); } protected T invoke(final Callable<T> call) throws Exception { @@ -76,6 +78,8 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa } public static final class Context { + public static final ThreadLocal<Context> CURRENT = new ThreadLocal<>(); + /* private static final Class<?>[] THREAD_SCOPES = new Class<?>[] { RequestScoped.class, SessionScoped.class, ConversationScoped.class @@ -87,6 +91,7 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa private final ClassLoader loader; private final boolean associate; private final AbstractSecurityService.SecurityContext securityContext; + private final Context stack; /* propagation of CDI context seems wrong private final CdiAppContextsService contextService; @@ -94,15 +99,17 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa */ private Context currentContext; + private Collection<Runnable> exitTasks; private Context(final boolean associate, final Object initialSecurityServiceState, final AbstractSecurityService.SecurityContext securityContext, final ThreadContext initialThreadContext, - final ClassLoader initialLoader) { + final ClassLoader initialLoader, final Context stack) { this.associate = associate; this.securityServiceState = initialSecurityServiceState; this.securityContext = securityContext; this.threadContext = initialThreadContext; this.loader = initialLoader; + this.stack = stack; /* propagation of CDI context seems wrong final ContextsService genericContextsService = WebBeansContext.currentInstance().getContextsService(); @@ -148,16 +155,26 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa oldCtx = null; } - currentContext = new Context(associate, threadState, securityContext, oldCtx, oldCl); + currentContext = new Context(associate, threadState, securityContext, oldCtx, oldCl, this); /* propagation of CDI context seems wrong if (cdiState != null) { contextService.restoreState(cdiState); } */ + + CURRENT.set(this); } public void exit() { + // exit tasks are designed to be in execution added post tasks so execution them before next ones + // ie inversed ordered compared to init phase + if (exitTasks != null) { + for (Runnable r : exitTasks) { + r.run(); + } + } + if (threadContext != null) { // ensure we use the same condition as point A, see OPENEJB-2109 ThreadContext.exit(currentContext.threadContext); } @@ -176,8 +193,20 @@ public abstract class CUTask<T> extends ManagedTaskListenerTask implements Compa */ Thread.currentThread().setContextClassLoader(currentContext.loader); + if (currentContext.stack == null) { + CURRENT.remove(); + } else { + CURRENT.set(currentContext.stack); + } currentContext = null; } + + public void pushExitTask(final Runnable runnable) { + if (exitTasks == null) { + exitTasks = new ArrayList<>(2); + } + exitTasks.add(runnable); + } } @Override http://git-wip-us.apache.org/repos/asf/tomee/blob/ed6331f1/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEERealm.java ---------------------------------------------------------------------- diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEERealm.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEERealm.java index dcd28ed..e86b58d 100644 --- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEERealm.java +++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEERealm.java @@ -18,10 +18,14 @@ package org.apache.tomee.catalina; import org.apache.catalina.Realm; import org.apache.catalina.Wrapper; +import org.apache.catalina.connector.Request; import org.apache.catalina.realm.CombinedRealm; import org.apache.catalina.realm.GenericPrincipal; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.SecurityService; +import org.apache.openejb.threads.task.CUTask; +import org.apache.openejb.util.LogCategory; +import org.apache.openejb.util.Logger; import org.ietf.jgss.GSSContext; import java.security.Principal; @@ -88,8 +92,27 @@ public class TomEERealm extends CombinedRealm { // normally we don't care about oldstate because the listener already contains one // which is the previous one // so no need to clean twice here - if (OpenEJBSecurityListener.requests.get() != null) { + final Request request = OpenEJBSecurityListener.requests.get(); + if (request != null) { ss.enterWebApp(this, pcp, OpenEJBSecurityListener.requests.get().getWrapper().getRunAs()); + } else { + final CUTask.Context context = CUTask.Context.CURRENT.get(); + if (context != null) { + final Object state = ss.enterWebApp(this, pcp, null); + context.pushExitTask(new Runnable() { + @Override + public void run() { + ss.exitWebApp(state); + } + }); + } else { + final Logger instance = Logger.getInstance(LogCategory.OPENEJB_SECURITY, TomEERealm.class); + if (instance.isDebugEnabled()) { + instance.debug( + "No request or concurrency-utilities context so skipping login context propagation, " + + "thread=" + Thread.currentThread().getName()); + } + } } } return pcp;
