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 <[email protected]> Authored: Tue Dec 6 11:19:49 2016 +0100 Committer: rmannibucau <[email protected]> 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(); } }
