/* * JBoss, Home of Professional Open Source. * Copyright 2011, 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 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.naming; import static org.junit.Assert.fail; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; import java.security.Permission; import java.security.Permissions; import java.security.Policy; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.concurrent.Callable; import javax.naming.CompositeName; import javax.naming.Name; import javax.naming.NamingException; import org.wildfly.naming.java.permission.JndiPermission; /** * @author Lukas Krejci */ public class SecurityHelper { private SecurityHelper() { } public static Object testActionPermission(final int action, final NamingContext namingContext, final String name, final Object... params) throws Exception { return testActionPermission(action, Collections.<JndiPermission>emptyList(), namingContext, name, params); } public static Object testActionPermission(final int action, final Collection<JndiPermission> additionalRequiredPerms, final NamingContext namingContext, final String name, final Object... params) throws Exception { Exception positiveTestCaseException = null; try { //positive test case return testActionWithPermission(action, additionalRequiredPerms, namingContext, name, params); } catch (Exception e) { positiveTestCaseException = e; //this is just to satisfy the compiler... the finally clause should always throw an exception in this case return null; } finally { //negative test case try { testActionWithoutPermission(action, additionalRequiredPerms, namingContext, name, params); } catch (Exception e) { if (positiveTestCaseException == null) { throw e; } else { throw new Exception("Both positive and negative permission test for JNDI action " + action + " failed. The negative test case (which should have resulted in a security exception)" + " failed with a message: " + "(" + e.getClass().getName() + "): " + e.getMessage() + ". The exception of the positive testcase" + " is set up as the cause of this exception.", positiveTestCaseException); } } if (positiveTestCaseException != null) { throw positiveTestCaseException; } } } public static Object testActionWithPermission(final int action, final Collection<JndiPermission> additionalRequiredPerms, final NamingContext namingContext, final String name, final Object... params) throws Exception { final CompositeName n = name == null ? new CompositeName() : new CompositeName(name); final String sn = name == null ? "" : name; ArrayList<JndiPermission> allPerms = new ArrayList<JndiPermission>(additionalRequiredPerms); allPerms.add(new JndiPermission(sn, action)); return runWithSecurityManager(new Callable<Object>() { @Override public Object call() throws Exception { return performAction(action, namingContext, n, params); } }, getSecurityContextForJNDILookup(allPerms)); } public static void testActionWithoutPermission(final int action, final Collection<JndiPermission> additionalRequiredPerms, final NamingContext namingContext, final String name, final Object... params) throws Exception { final CompositeName n = name == null ? new CompositeName() : new CompositeName(name); final String sn = name == null ? "" : name; ArrayList<JndiPermission> allPerms = new ArrayList<JndiPermission>(additionalRequiredPerms); allPerms.add(new JndiPermission(sn, not(action))); try { runWithSecurityManager(new Callable<Object>() { @Override public Object call() throws Exception { return performAction(action, namingContext, n, params); } }, getSecurityContextForJNDILookup(allPerms)); fail("Naming operation " + action + " should not have been permitted"); } catch (SecurityException e) { //expected } } private static int not(int action) { return ~action & JndiPermission.ACTION_ALL; } private static Object performAction(int action, NamingContext namingContext, Name name, Object... params) throws NamingException { switch (action) { case JndiPermission.ACTION_BIND: if (params.length == 1) { namingContext.bind(name, params[0]); } else { throw new IllegalArgumentException("Invalid number of arguments passed to bind()"); } return null; case JndiPermission.ACTION_CREATE_SUBCONTEXT: return namingContext.createSubcontext(name); case JndiPermission.ACTION_LIST: return namingContext.list(name); case JndiPermission.ACTION_LIST_BINDINGS: return namingContext.listBindings(name); case JndiPermission.ACTION_LOOKUP: if (params.length == 0) { return namingContext.lookup(name); } else if (params.length == 1) { return namingContext.lookup(name, (Boolean) params[0]); } else { throw new IllegalArgumentException("Invalid number of arguments passed to lookup()"); } case JndiPermission.ACTION_REBIND: if (params.length == 1) { namingContext.rebind(name, params[0]); } else { throw new IllegalArgumentException("Invalid number of arguments passed to rebind()"); } return null; case JndiPermission.ACTION_UNBIND: namingContext.unbind(name); return null; default: throw new IllegalArgumentException("Action " + action + " not supported by the generic testActionPermission test"); } } public static <T> T runWithSecurityManager(final Callable<T> action, final AccessControlContext securityContext) throws Exception { Policy previousPolicy = Policy.getPolicy(); SecurityManager previousSM = System.getSecurityManager(); //let's be a bit brutal here and just allow any code do anything by default for the time this method executes. Policy.setPolicy(new Policy() { @Override public boolean implies(ProtectionDomain domain, Permission permission) { return true; } }); //with our new totally unsecure policy, let's install a new security manager System.setSecurityManager(new SecurityManager()); try { //run the code to test with limited privs defined by the securityContext return AccessController.doPrivileged(new PrivilegedExceptionAction<T>() { @Override public T run() throws Exception { return action.call(); } }, securityContext); } catch (PrivilegedActionException e) { throw e.getException(); } finally { //and reset back the previous security settings System.setSecurityManager(previousSM); Policy.setPolicy(previousPolicy); } } private static AccessControlContext getSecurityContextForJNDILookup(Collection<JndiPermission> jndiPermissions) { CodeSource src = new CodeSource(null, (Certificate[]) null); Permissions perms = new Permissions(); for (JndiPermission p : jndiPermissions) { perms.add(p); } ProtectionDomain domain = new ProtectionDomain(src, perms); AccessControlContext ctx = new AccessControlContext(new ProtectionDomain[]{domain}); return ctx; } }