diff --git a/ejb3/src/main/java/org/jboss/as/ejb3/component/singleton/SingletonComponentDescription.java b/ejb3/src/main/java/org/jboss/as/ejb3/component/singleton/SingletonComponentDescription.java index 2e752f5e56a7..7779f5683b91 100644 --- a/ejb3/src/main/java/org/jboss/as/ejb3/component/singleton/SingletonComponentDescription.java +++ b/ejb3/src/main/java/org/jboss/as/ejb3/component/singleton/SingletonComponentDescription.java @@ -51,6 +51,7 @@ import org.jboss.as.ejb3.component.session.StatelessWriteReplaceInterceptor; import org.jboss.as.ejb3.concurrency.ContainerManagedConcurrencyInterceptorFactory; import org.jboss.as.ejb3.deployment.EjbJarDescription; +import org.jboss.as.ejb3.security.SecurityContextInterceptorFactory; import org.jboss.as.ejb3.tx.EjbBMTInterceptor; import org.jboss.as.ejb3.tx.LifecycleCMTTxInterceptor; import org.jboss.as.ejb3.tx.TimerCMTTxInterceptor; @@ -106,7 +107,12 @@ public ComponentConfiguration createConfiguration(final ClassIndex classIndex, f ComponentConfiguration singletonComponentConfiguration = new ComponentConfiguration(this, classIndex, moduleClassLoader, moduleLoader); // setup the component create service singletonComponentConfiguration.setComponentCreateServiceFactory(new SingletonComponentCreateServiceFactory(this.isInitOnStartup(), dependsOn)); - + getConfigurators().add(new ComponentConfigurator() { + @Override + public void configure(final DeploymentPhaseContext context, final ComponentDescription description, final ComponentConfiguration configuration) throws DeploymentUnitProcessingException { + configuration.addPostConstructInterceptor(new SecurityContextInterceptorFactory(isSecurityEnabled(), false), InterceptorOrder.View.SECURITY_CONTEXT); + } + }); if (getTransactionManagementType().equals(TransactionManagementType.CONTAINER)) { //we need to add the transaction interceptor to the lifecycle methods getConfigurators().add(new ComponentConfigurator() { diff --git a/ejb3/src/main/java/org/jboss/as/ejb3/security/SecurityContextInterceptorFactory.java b/ejb3/src/main/java/org/jboss/as/ejb3/security/SecurityContextInterceptorFactory.java index f9900d15b840..51ebe36b1a17 100644 --- a/ejb3/src/main/java/org/jboss/as/ejb3/security/SecurityContextInterceptorFactory.java +++ b/ejb3/src/main/java/org/jboss/as/ejb3/security/SecurityContextInterceptorFactory.java @@ -31,6 +31,7 @@ import org.jboss.as.ee.component.Component; import org.jboss.as.ee.component.ComponentInterceptorFactory; import org.jboss.as.ejb3.component.EJBComponent; +import org.jboss.as.security.service.SimpleSecurityManager; import org.jboss.invocation.Interceptor; import org.jboss.invocation.InterceptorFactoryContext; import org.jboss.metadata.javaee.spec.SecurityRolesMetaData; @@ -43,9 +44,15 @@ public class SecurityContextInterceptorFactory extends ComponentInterceptorFacto private static final String DEFAULT_DOMAIN = "other"; private final boolean securityRequired; + private final boolean propagateSecurity; public SecurityContextInterceptorFactory(final boolean securityRequired) { + this(securityRequired, true); + } + + public SecurityContextInterceptorFactory(final boolean securityRequired, final boolean propagateSecurity) { this.securityRequired = securityRequired; + this.propagateSecurity = propagateSecurity; } @Override @@ -54,7 +61,12 @@ protected Interceptor create(final Component component, final InterceptorFactory throw MESSAGES.unexpectedComponent(component, EJBComponent.class); } final EJBComponent ejbComponent = (EJBComponent) component; - final ServerSecurityManager securityManager = ejbComponent.getSecurityManager(); + final ServerSecurityManager securityManager; + if(propagateSecurity) { + securityManager = ejbComponent.getSecurityManager(); + } else { + securityManager = new SimpleSecurityManager((SimpleSecurityManager) ejbComponent.getSecurityManager()); + } final EJBSecurityMetaData securityMetaData = ejbComponent.getSecurityMetaData(); String securityDomain = securityMetaData.getSecurityDomain(); if (securityDomain == null) { diff --git a/security/src/main/java/org/jboss/as/security/service/SimpleSecurityManager.java b/security/src/main/java/org/jboss/as/security/service/SimpleSecurityManager.java index 53396628b002..e71ead041fb4 100644 --- a/security/src/main/java/org/jboss/as/security/service/SimpleSecurityManager.java +++ b/security/src/main/java/org/jboss/as/security/service/SimpleSecurityManager.java @@ -78,6 +78,21 @@ */ public class SimpleSecurityManager implements ServerSecurityManager { private ThreadLocalStack contexts = new ThreadLocalStack(); + /** + * Indicates if we propagate previous SecurityContext informations to the current one or not. + * This was introduced for WFLY-981 where we wanted to have a clean SecurityContext using only the Singleton EJB security + * configuration and not what it might have 'inherited' in the execution stack during a Postconstruct method call. + * @see org.jboss.as.ejb3.security.NonPropagatingSecurityContextInterceptorFactory + */ + private boolean propagate = true; + + public SimpleSecurityManager() { + } + + public SimpleSecurityManager(SimpleSecurityManager delegate) { + this.securityManagement = delegate.securityManagement; + this.propagate = false; + } private ISecurityManagement securityManagement = null; @@ -269,7 +284,7 @@ public void push(final String securityDomain) { final SecurityContext previous = SecurityContextAssociation.getSecurityContext(); contexts.push(previous); SecurityContext current = establishSecurityContext(securityDomain); - if (previous != null) { + if (propagate && previous != null) { current.setSubjectInfo(getSubjectInfo(previous)); current.setIncomingRunAs(previous.getOutgoingRunAs()); } @@ -318,7 +333,7 @@ public void push(final String securityDomain, String userName, char[] password, final SecurityContext previous = SecurityContextAssociation.getSecurityContext(); contexts.push(previous); SecurityContext current = establishSecurityContext(securityDomain); - if (previous != null) { + if (propagate && previous != null) { current.setSubjectInfo(getSubjectInfo(previous)); current.setIncomingRunAs(previous.getOutgoingRunAs()); } @@ -354,7 +369,7 @@ public void authenticate(final String runAs, final String runAsPrincipal, final if (runAs != null) { RunAs runAsIdentity = new RunAsIdentity(runAs, runAsPrincipal, extraRoles); context.setOutgoingRunAs(runAsIdentity); - } else if (previous != null && previous.getOutgoingRunAs() != null) { + } else if (propagate && previous != null && previous.getOutgoingRunAs() != null) { // Ensure the propagation continues. context.setOutgoingRunAs(previous.getOutgoingRunAs()); } diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/RunAsPrincipalTestCase.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/RunAsPrincipalTestCase.java index 4beefd8fcb36..fa7437bfd1c5 100644 --- a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/RunAsPrincipalTestCase.java +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/RunAsPrincipalTestCase.java @@ -23,15 +23,23 @@ import javax.ejb.EJBAccessException; import javax.naming.InitialContext; +import org.hamcrest.CoreMatchers; +import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.OperateOnDeployment; import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.as.arquillian.api.ServerSetup; import org.jboss.as.test.categories.CommonCriteria; import org.jboss.as.test.integration.ejb.security.runasprincipal.Caller; import org.jboss.as.test.integration.ejb.security.runasprincipal.CallerWithIdentity; -import org.jboss.as.test.integration.ejb.security.runasprincipal.SingletonCallerBean; +import org.jboss.as.test.integration.ejb.security.runasprincipal.SingletonBean; +import org.jboss.as.test.integration.ejb.security.runasprincipal.StatelessBBean; import org.jboss.as.test.integration.ejb.security.runasprincipal.WhoAmI; +import org.jboss.as.test.integration.ejb.security.runasprincipal.transitive.SimpleSingletonBean; +import org.jboss.as.test.integration.ejb.security.runasprincipal.transitive.SingletonStartupBean; +import org.jboss.as.test.integration.ejb.security.runasprincipal.transitive.StatelessSingletonUseBean; import org.jboss.as.test.integration.security.common.AbstractSecurityDomainSetup; import org.jboss.as.test.shared.integration.ejb.security.Util; import org.jboss.logging.Logger; @@ -59,21 +67,46 @@ public class RunAsPrincipalTestCase { private static final Logger log = Logger.getLogger(RunAsPrincipalTestCase.class); + private static final String STARTUP_SINGLETON_DEPLOYMENT = "startup-transitive-singleton"; + private static final String DEPLOYMENT = "runasprincipal-test"; + + @ArquillianResource + public Deployer deployer; + + @Deployment(name = STARTUP_SINGLETON_DEPLOYMENT, managed = false, testable = false) + public static Archive runAsStartupTransitiveDeployment() { + // using JavaArchive doesn't work, because of a bug in Arquillian, it only deploys wars properly + final WebArchive war = ShrinkWrap.create(WebArchive.class, STARTUP_SINGLETON_DEPLOYMENT + ".war") + .addClass(WhoAmI.class) + .addClass(StatelessBBean.class) + .addClass(SingletonStartupBean.class) + .addPackage(Assert.class.getPackage()) + .addClass(Util.class) + .addClass(Entry.class) + .addClass(RunAsPrincipalTestCase.class) + .addClass(Base64.class) + .addClasses(AbstractSecurityDomainSetup.class, EjbSecurityDomainSetup.class) + .addAsWebInfResource(RunAsPrincipalTestCase.class.getPackage(), "jboss-ejb3.xml", "jboss-ejb3.xml") + .addAsManifestResource(new StringAsset("Dependencies: org.jboss.as.controller-client,org.jboss.dmr\n"), "MANIFEST.MF"); + war.addPackage(CommonCriteria.class.getPackage()); + return war; + } @Deployment public static Archive runAsDeployment() { // using JavaArchive doesn't work, because of a bug in Arquillian, it only deploys wars properly - final WebArchive war = ShrinkWrap.create(WebArchive.class, "runasprincipal-test.war") + final WebArchive war = ShrinkWrap.create(WebArchive.class, DEPLOYMENT + ".war") .addPackage(WhoAmI.class.getPackage()) + .addClass(SimpleSingletonBean.class) + .addClass(StatelessSingletonUseBean.class) .addClass(Util.class) .addClass(Entry.class) .addClass(RunAsPrincipalTestCase.class) .addClass(Base64.class) .addClasses(AbstractSecurityDomainSetup.class, EjbSecurityDomainSetup.class) .addAsWebInfResource(RunAsPrincipalTestCase.class.getPackage(), "jboss-ejb3.xml", "jboss-ejb3.xml") - .addAsManifestResource(new StringAsset("Dependencies: org.jboss.as.controller-client,org.jboss.dmr\n"),"MANIFEST.MF"); + .addAsManifestResource(new StringAsset("Dependencies: org.jboss.as.controller-client,org.jboss.dmr\n"), "MANIFEST.MF"); war.addPackage(CommonCriteria.class.getPackage()); - log.info(war.toString(true)); return war; } @@ -82,13 +115,17 @@ private WhoAmI lookupCallerWithIdentity() throws Exception { } private WhoAmI lookupSingleCallerWithIdentity() throws Exception { - return (WhoAmI)new InitialContext().lookup("java:module/" + SingletonCallerBean.class.getSimpleName() + "!" + WhoAmI.class.getName()); + return (WhoAmI)new InitialContext().lookup("java:module/" + SingletonBean.class.getSimpleName() + "!" + WhoAmI.class.getName()); } private WhoAmI lookupCaller() throws Exception { return (WhoAmI)new InitialContext().lookup("java:module/" + Caller.class.getSimpleName() + "!" + WhoAmI.class.getName()); } + private WhoAmI lookupSingletonUseBeanWithIdentity() throws Exception { + return (WhoAmI) new InitialContext().lookup("java:module/" + StatelessSingletonUseBean.class.getSimpleName() + "!" + WhoAmI.class.getName()); + } + @Test public void testJackInABox() throws Exception { SecurityClient client = SecurityClientFactory.getSecurityClient(); @@ -104,12 +141,12 @@ public void testJackInABox() throws Exception { } @Test - public void testSingletonSecurity() throws Exception { + public void testSingletonPostconstructSecurity() throws Exception { SecurityClient client = SecurityClientFactory.getSecurityClient(); client.setSimple("user1", "password1"); client.login(); try { - WhoAmI bean = lookupSingleCallerWithIdentity(); + WhoAmI bean = lookupSingleCallerWithIdentity(); String actual = bean.getCallerPrincipal(); Assert.assertEquals("Helloween", actual); } finally { @@ -142,4 +179,47 @@ public void testAnonymous() throws Exception { client.logout(); } } + + @Test + @OperateOnDeployment(STARTUP_SINGLETON_DEPLOYMENT) + public void testStartupSingletonPostconstructSecurityNotPropagating() { + try { + deployer.deploy(STARTUP_SINGLETON_DEPLOYMENT); + Assert.fail("Deployment should fail"); + } catch (Exception dex) { + Throwable t = checkEjbException(dex); + log.info("Expected deployment error because the Singleton has nosecurity context per itself", dex.getCause()); + Assert.assertThat(t.getMessage(), t.getMessage(), CoreMatchers.containsString("JBAS014502")); + } finally { + deployer.undeploy(STARTUP_SINGLETON_DEPLOYMENT); + } + } + + @Test + public void testSingletonPostconstructSecurityNotPropagating() throws Exception { + SecurityClient client = SecurityClientFactory.getSecurityClient(); + client.setSimple("user1", "password1"); + client.login(); + try { + WhoAmI bean = lookupSingletonUseBeanWithIdentity(); //To load the singleton + bean.getCallerPrincipal(); + Assert.fail("Deployment should fail"); + } catch (Exception dex) { + Throwable t = checkEjbException(dex); + log.info("Expected deployment error because the Singleton has nosecurity context per itself", dex.getCause()); + Assert.assertThat(t.getMessage(), t.getMessage(), CoreMatchers.containsString("JBAS014502")); + } finally { + client.logout(); + } + } + + private Throwable checkEjbException(Throwable ex) { + if (ex instanceof EJBAccessException) { + return ex; + } + if (ex.getCause() != null) { + return checkEjbException(ex.getCause()); + } + return ex; + } } diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/SingletonCallerBean.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/SingletonBean.java similarity index 80% rename from testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/SingletonCallerBean.java rename to testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/SingletonBean.java index 77c02dafbd0c..626d9c68553a 100644 --- a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/SingletonCallerBean.java +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/SingletonBean.java @@ -20,14 +20,17 @@ */ package org.jboss.as.test.integration.ejb.security.runasprincipal; +import javax.annotation.PostConstruct; import javax.annotation.security.RolesAllowed; import javax.annotation.security.RunAs; import javax.ejb.EJB; import javax.ejb.Remote; import javax.ejb.Singleton; import javax.ejb.Startup; +import org.jboss.as.test.integration.ejb.security.runasprincipal.WhoAmI; import org.jboss.ejb3.annotation.RunAsPrincipal; import org.jboss.ejb3.annotation.SecurityDomain; +import org.junit.Assert; /** * @@ -40,11 +43,19 @@ @RunAs("Admin") @RunAsPrincipal("Helloween") @SecurityDomain("other") -public class SingletonCallerBean implements WhoAmI { +public class SingletonBean implements WhoAmI { @EJB(beanName = "StatelessBBean") private WhoAmI beanB; + private String principal; + + @PostConstruct + public void init() { + principal = beanB.getCallerPrincipal(); + Assert.assertEquals("Helloween", principal); + } + public String getCallerPrincipal() { - return beanB.getCallerPrincipal(); + return principal; } } diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/SimpleSingletonBean.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/SimpleSingletonBean.java new file mode 100644 index 000000000000..4b2cad315764 --- /dev/null +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/SimpleSingletonBean.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file + * in the distribution for a full listing of individual contributors. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jboss.as.test.integration.ejb.security.runasprincipal.transitive; + +import javax.annotation.PostConstruct; +import javax.ejb.EJB; +import javax.ejb.Remote; +import javax.ejb.Singleton; +import org.jboss.as.test.integration.ejb.security.runasprincipal.WhoAmI; +import org.jboss.ejb3.annotation.SecurityDomain; + +/** + * + * @author Emmanuel Hugonnet (c) 2013 Red Hat, inc. + */ +@Singleton +@Remote(WhoAmI.class) +@SecurityDomain("other") +public class SimpleSingletonBean implements WhoAmI { + + @EJB(beanName = "StatelessBBean") + private WhoAmI beanB; + + private String principal; + + @PostConstruct + public void init() { + principal = beanB.getCallerPrincipal(); + } + + public String getCallerPrincipal() { + return principal; + } + +} diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/SingletonStartupBean.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/SingletonStartupBean.java new file mode 100644 index 000000000000..a982c5056b91 --- /dev/null +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/SingletonStartupBean.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file + * in the distribution for a full listing of individual contributors. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jboss.as.test.integration.ejb.security.runasprincipal.transitive; + +import javax.annotation.PostConstruct; +import javax.ejb.EJB; +import javax.ejb.Remote; +import javax.ejb.Singleton; +import javax.ejb.Startup; +import org.jboss.as.test.integration.ejb.security.runasprincipal.WhoAmI; +import org.jboss.ejb3.annotation.SecurityDomain; +import org.junit.Assert; + +/** + * + * @author Emmanuel Hugonnet (c) 2013 Red Hat, inc. + */ +@Singleton +@Startup +@Remote(WhoAmI.class) +@SecurityDomain("other") +public class SingletonStartupBean implements WhoAmI { + + @EJB(beanName = "StatelessBBean") + private WhoAmI beanB; + + private String principal; + + @PostConstruct + public void init() { + principal = beanB.getCallerPrincipal(); + Assert.fail("beanB requires role Admin"); + } + + public String getCallerPrincipal() { + return principal; + } + +} diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/StatelessSingletonUseBean.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/StatelessSingletonUseBean.java new file mode 100644 index 000000000000..4bc81a472799 --- /dev/null +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/ejb/security/runasprincipal/transitive/StatelessSingletonUseBean.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 Red Hat, inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +package org.jboss.as.test.integration.ejb.security.runasprincipal.transitive; + +import javax.annotation.security.RunAs; +import javax.ejb.EJB; +import javax.ejb.Remote; +import javax.ejb.Stateless; +import org.jboss.as.test.integration.ejb.security.runasprincipal.WhoAmI; +import org.jboss.ejb3.annotation.RunAsPrincipal; +import org.jboss.ejb3.annotation.SecurityDomain; + +/** + * + * @author Emmanuel Hugonnet (c) 2013 Red Hat, inc. + */ + +@Stateless +@Remote(WhoAmI.class) +@RunAs("Admin") +@RunAsPrincipal("IronMaiden") +@SecurityDomain("other") +public class StatelessSingletonUseBean implements WhoAmI { + + @EJB(beanName = "SimpleSingletonBean") + private WhoAmI singleton; + + public String getCallerPrincipal() { + return singleton.getCallerPrincipal(); + } +} \ No newline at end of file