org.rhq.enterprise.server.naming.AccessCheckingInitialContextFactoryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.enterprise.server.naming.AccessCheckingInitialContextFactoryBuilder.java

Source

/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.server.naming;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.event.EventContext;
import javax.naming.event.EventDirContext;
import javax.naming.ldap.LdapContext;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.enterprise.server.AllowRhqServerInternalsAccessPermission;
import org.rhq.enterprise.server.naming.context.AccessCheckingContextDecorator;
import org.rhq.enterprise.server.naming.context.AccessCheckingContextDecoratorSetContext;
import org.rhq.enterprise.server.naming.context.ContextDecorator;
import org.rhq.enterprise.server.naming.context.URLPreferringContextDecoratorSetContext;
import org.rhq.enterprise.server.naming.util.DecoratorPicker;

/**
 * This initial context factory builder is installed early on during the RHQ server startup
 * and is later on used for obtaining the {@link Context}s for all JNDI lookups in the
 * RHQ server.
 * <p>
 * We use a custom initial context factory builder to prevent the potential malicious 3rd party
 * code (like CLI alert scripts) from supplying custom environment variables to {@link InitialContext} 
 * that would modify the JNDI lookup to skip our security access checks.
 * <p>
 * By using a builder we effectively take control of the initial context creation process
 * and are free to ignore whatever the script is trying to supply.
 * <p>
 * This builder makes sure to install the RHQ server's security access checks to whatever
 * initial context that is configured by the standard environment variables 
 * ({@link Context#INITIAL_CONTEXT_FACTORY}, etc.)
 * 
 * @see AllowRhqServerInternalsAccessPermission
 * 
 * @author Lukas Krejci
 */
public class AccessCheckingInitialContextFactoryBuilder implements InitialContextFactoryBuilder {
    private static final Log LOG = LogFactory.getLog(AccessCheckingInitialContextFactoryBuilder.class);

    /**
     * The list of JNDI name schemes that should be checked for security permissions
     * (in addition to the names with no scheme).
     * 
     * @see AccessCheckingContextDecorator
     */
    private static final String[] CHECKED_SCHEMES = { "java" };

    private static final Set<Class<? extends Context>> SUPPORTED_CONTEXT_INTERFACES;

    static {
        SUPPORTED_CONTEXT_INTERFACES = new HashSet<Class<? extends Context>>();
        SUPPORTED_CONTEXT_INTERFACES.add(Context.class);
        SUPPORTED_CONTEXT_INTERFACES.add(DirContext.class);
        SUPPORTED_CONTEXT_INTERFACES.add(EventContext.class);
        SUPPORTED_CONTEXT_INTERFACES.add(EventDirContext.class);
        SUPPORTED_CONTEXT_INTERFACES.add(LdapContext.class);
    }

    private static final Set<InetAddress> SERVER_BIND_IPS;
    static {
        SERVER_BIND_IPS = new HashSet<InetAddress>();

        try {
            String bindingAddressString = System.getProperty("jboss.bind.address");
            InetAddress bindingAddress = InetAddress.getByName(bindingAddressString);

            if (bindingAddress.isAnyLocalAddress()) {
                Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
                while (ifaces.hasMoreElements()) {
                    NetworkInterface iface = ifaces.nextElement();
                    SERVER_BIND_IPS.addAll(Collections.list(iface.getInetAddresses()));
                }
            } else {
                SERVER_BIND_IPS.add(bindingAddress);
            }
        } catch (SocketException e) {
            LOG.error("Could not obtain the list of local IPs", e);
        } catch (UnknownHostException e) {
            LOG.error("Failed to get the binding address of the RHQ server.", e);
        }
    }

    //TODO this most probably no longer applies because AS7 doesn't use JNP for JNDI
    private static final int JNP_PORT = Integer
            .parseInt(System.getProperty("rhq.server.startup.namingservice.port", "2099"));

    private static InitialContextFactory getJbossDefaultInitialContextFactory() throws NamingException {
        try {
            Class<?> cls = Class.forName("org.jboss.as.naming.InitialContextFactory");
            return (InitialContextFactory) cls.newInstance();
        } catch (Exception e) {
            NamingException ne = new NamingException(
                    "Failed to obtain the default initial context factory from JBoss AS.");
            ne.initCause(e);

            throw ne;
        }
    }

    private enum FactoryType {
        ACCESS_CHECKING_URL_PREFERRING {

            @Override
            public InitialContextFactory wrap(InitialContextFactory factory) {
                ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context, ContextDecorator>>();
                pickers.add(getURLPreferringDecoratorPicker());
                pickers.add(getAccessCheckingDecoratorPicker());

                return new DecoratingInitialContextFactory(factory, pickers);
            }
        },
        URL_PREFERRING {

            @Override
            public InitialContextFactory wrap(InitialContextFactory factory) {
                ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context, ContextDecorator>>();
                pickers.add(getURLPreferringDecoratorPicker());

                return new DecoratingInitialContextFactory(factory, pickers);
            }
        },
        ACCESS_CHECKING {

            @Override
            public InitialContextFactory wrap(InitialContextFactory factory) {
                ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context, ContextDecorator>>();
                pickers.add(getAccessCheckingDecoratorPicker());

                return new DecoratingInitialContextFactory(factory, pickers);
            }
        },
        PASS_THROUGH {

            @Override
            public InitialContextFactory wrap(InitialContextFactory factory) {
                return factory;
            }
        };

        public abstract InitialContextFactory wrap(InitialContextFactory factory);

        public static FactoryType detect(Hashtable<?, ?> environment, boolean pretendNoFactoryBuilder) {
            String providerUrl = (String) environment.get(Context.PROVIDER_URL);

            if (providerUrl == null) {
                return pretendNoFactoryBuilder ? ACCESS_CHECKING_URL_PREFERRING : ACCESS_CHECKING;
            } else {
                try {
                    URI uri = new URI(providerUrl);
                    InetAddress providerHost = InetAddress.getByName(uri.getHost());

                    //check if we are accessing the RHQ server through some remoting
                    //interface.
                    if (uri.getPort() == JNP_PORT && SERVER_BIND_IPS.contains(providerHost)) {
                        return pretendNoFactoryBuilder ? ACCESS_CHECKING_URL_PREFERRING : ACCESS_CHECKING;
                    } else {
                        return pretendNoFactoryBuilder ? URL_PREFERRING : PASS_THROUGH;
                    }
                } catch (URISyntaxException e) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(
                                "The " + Context.PROVIDER_URL
                                        + " is not a valid URI. Falling back to using the access checking wrapper.",
                                e);
                    }
                    return pretendNoFactoryBuilder ? ACCESS_CHECKING_URL_PREFERRING : ACCESS_CHECKING;
                } catch (UnknownHostException e) {
                    //let the factory deal with the unknown host...
                    //this most probably shouldn't be secured because localhost addresses
                    //should be resolvable.
                    if (LOG.isDebugEnabled()) {
                        LOG.debug(
                                "The " + Context.PROVIDER_URL
                                        + " is not resolvable. Falling back to using the URL preferring wrapper.",
                                e);
                    }
                    return pretendNoFactoryBuilder ? URL_PREFERRING : PASS_THROUGH;
                }
            }
        }

        private static DecoratorPicker<Context, ContextDecorator> getAccessCheckingDecoratorPicker() {
            DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>();
            ret.setContext(
                    new AccessCheckingContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES, CHECKED_SCHEMES));

            return ret;
        }

        private static DecoratorPicker<Context, ContextDecorator> getURLPreferringDecoratorPicker() {
            DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>();
            ret.setContext(new URLPreferringContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES));

            return ret;
        }
    }

    private final EnumMap<FactoryType, InitialContextFactory> typeDefaults = new EnumMap<FactoryType, InitialContextFactory>(
            FactoryType.class);

    private final String defaultFactoryClassName;
    private final boolean pretendNoFactoryBuilder;

    /**
     * @param defaultFactory the default factory to use if none can be deduced from the environment. If null, an attempt
     * is made to obtain the default InitialContextFactory of JBoss AS (which may fail depending on the classloading
     * "situation").
     * @param pretendNoFactoryBuilder true if the naming contexts should pretend as if there was no initial context
     * factory builder installed. This is to support environments as AS4, where there really was no builder initially
     * and the lookup relied on that fact.
     * 
     * @throws NamingException
     */
    public AccessCheckingInitialContextFactoryBuilder(InitialContextFactory defaultFactory,
            final boolean pretendNoFactoryBuilder) throws NamingException {
        if (defaultFactory == null) {
            defaultFactory = getJbossDefaultInitialContextFactory();
        }

        defaultFactoryClassName = defaultFactory.getClass().getName();

        for (FactoryType ft : FactoryType.values()) {
            typeDefaults.put(ft, ft.wrap(defaultFactory));
        }

        this.pretendNoFactoryBuilder = pretendNoFactoryBuilder;

        this.defaultFactory = new InitialContextFactory() {
            public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
                return typeDefaults.get(FactoryType.detect(environment, pretendNoFactoryBuilder))
                        .getInitialContext(environment);
            }
        };
    }

    /**
     * This is the default initial context factory that is returned when no other is 
     * configured using the environment variables.
     */
    private final InitialContextFactory defaultFactory;

    /**
     * Create a InitialContext factory.  If the environment does not override the factory class it will use the
     * default context factory.
     * 
     * @param environment The environment
     * @return An initial context factory
     * @throws NamingException If an error occurs loading the factory class.
     */
    public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
        final String factoryClassName = (String) environment.get(Context.INITIAL_CONTEXT_FACTORY);
        if (factoryClassName == null || factoryClassName.equals(defaultFactoryClassName)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No " + Context.INITIAL_CONTEXT_FACTORY + " set. Using the default factory.");
            }
            return defaultFactory;
        }
        final ClassLoader classLoader = getContextClassLoader();
        try {
            final Class<?> factoryClass = Class.forName(factoryClassName, true, classLoader);
            InitialContextFactory configuredFactory = (InitialContextFactory) factoryClass.newInstance();
            return FactoryType.detect(environment, pretendNoFactoryBuilder).wrap(configuredFactory);
        } catch (Exception e) {
            NamingException ne = new NamingException("Failed instantiate InitialContextFactory " + factoryClassName
                    + " from classloader " + classLoader);
            ne.initCause(e);

            throw ne;
        }
    }

    private ClassLoader getContextClassLoader() {
        return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
            public ClassLoader run() {
                return Thread.currentThread().getContextClassLoader();
            }
        });
    }
}