org.josso.jb32.agent.JBossCatalinaRealm.java Source code

Java tutorial

Introduction

Here is the source code for org.josso.jb32.agent.JBossCatalinaRealm.java

Source

/*
 * JOSSO: Java Open Single Sign-On
 *
 * Copyright 2004-2009, Atricore, Inc.
 *
 * 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.josso.jb32.agent;

import org.apache.catalina.realm.GenericPrincipal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.naming.Util;
import org.jboss.security.AuthenticationManager;
import org.jboss.security.RealmMapping;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SubjectSecurityManager;
import org.jboss.security.plugins.JaasSecurityManager;
import org.jboss.util.CachePolicy;
import org.jboss.web.tomcat.security.JBossSecurityMgrRealm;
import org.jboss.web.tomcat.security.SecurityAssociationValve;
import org.josso.gateway.identity.SSOUser;
import org.josso.tc50.agent.jaas.CatalinaSSOUser;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import java.lang.reflect.Method;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

/**
 * JBoss Realm proxy that does mainly the following two things :
 *
 * <p>
 * 1. Before invoking the overriden Realm methods, it creates a "java:comp/env/security" JNDI context
 *    needed by the JBossSecurityMgrRealm to retrieve the configured JBoss Security Manager.
 *    The "java:comp/env/security" context is only created by Catalina for built-in authenticators
 *    and web applications contexts. The Context where the Agent Valve is associated to does not have
 *    an ENC at all so we must build one for it.
 * <p>
 * 2. Completely overrides the user authentication method so that the current Principal is not
 *    the SSO Session Id Principal but the SSOUser Principal.
 *
 * <p>
 * All Realm operations that require a SecurityContext were overriden so that there is a chance
 * for our Realm to prepare the "java:comp/env/security" JNDI Context.
 * <p>
 *
 * @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a>
 * @version CVS $Id: JBossCatalinaRealm.java 974 2009-01-14 00:39:45Z sgonzalez $
 */
public class JBossCatalinaRealm extends JBossSecurityMgrRealm {
    private static final Log logger = LogFactory.getLog(JBossCatalinaRealm.class);

    /** The fixed JOSSO JBoss Security Domain Name */
    private static final String JOSSO_SECURITY_DOMAIN = "java:/jaas/josso";

    private static final String DEFAULT_CACHE_POLICY_PATH = "java:/timedCacheFactory";

    /** The location of the security credential cache policy. This is first treated
     as a ObjectFactory location that is capable of returning CachePolicy instances
     on a per security domain basis by appending a '/security-domain-name' string
     to this name when looking up the CachePolicy for a domain. If this fails then
     the location is treated as a single CachePolicy for all security domains.
     */
    private static String cacheJndiName = DEFAULT_CACHE_POLICY_PATH;

    /** HashMap<UserPrincipal, AuthPrincipal> */
    private HashMap _userPrincipalMap = new HashMap();

    /** HashMap<SSOUserPrincipal, JossoSessionIdPrincipal> */
    /* private HashMap _userSessionMap = new HashMap();*/
    private SessionMappingCachePolicy _cachePolicy;

    /**
     *
     * @param domain the security domain name
     * @return true if this is a SSO security domain.
     */
    protected boolean isSSODomain(String domain) {

        boolean isSSODomain = "josso".equals(domain);

        if (logger.isDebugEnabled())
            logger.debug(" JBoss Security Domain [" + domain + "] is" + (isSSODomain ? "" : " not")
                    + " under SSO Control");

        return isSSODomain;
    }

    /**
     * Return the Principal associated with the specified username and
     * credentials, if there is one; otherwise return null.
     *
     * The method was completely rewritten since the overriden operation,
     * on succesfull authentication, sets as the authenticated Principal
     * a SimplePrincipal instantiated using the provided username.
     * The problem is that in JOSSO the username is a SSO Session Id, not
     * a username. So we need to set the SSOUser returned by the JAAS Gateway
     * Login Module as the authenticatd Principal.
     * Since the JaasSecurityManager caches the authenticated user using the
     * Principal referring to a JOSSO Session Id, we will need to map, for
     * example when roles are checked against the realm, a user Principal
     * back to its JOSSO Session Identifier Principal. This way the the user
     * and its roles can be retrieved correctly by the JaasSecurityManager.
     *
     * @param username Username of the Principal to look up
     * @param credentials Password or other credentials to use in
     * authenticating this username
     */
    public Principal authenticate(String username, String credentials) {

        logger.debug("Begin authenticate, username=" + username);

        Principal principal = null;
        SSOUser ssoUser = null;
        Principal caller = (Principal) SecurityAssociationValve.userPrincipal.get();
        if (caller == null && username == null && credentials == null)
            return null;

        try {
            Context securityCtx = null;
            securityCtx = prepareENC();

            if (securityCtx == null) {
                logger.error("No security context for authenticate(String, String)");
                return null;
            }

            // Get the JBoss security manager from the ENC context
            SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
            if (!isSSODomain(securityMgr.getSecurityDomain())) {
                // This is not a SSO Security domain, let JBoss realm handle this ...
                return super.authenticate(username, credentials);
            }

            principal = new SimplePrincipal(username);
            char[] passwordChars = null;
            if (credentials != null)
                passwordChars = credentials.toCharArray();

            Subject subject = new Subject();
            if (securityMgr.isValid(principal, passwordChars, subject)) {
                logger.debug("User: " + username + " is authenticated");

                // Get the authorized subject set by the isValid() call on succesful
                // authentication.
                // Subject activeSubject = securityMgr.getActiveSubject();

                // logger.debug("Authenticated Subject: " + activeSubject);

                logger.debug("Authenticated Subject: " + subject);

                Set principals = subject.getPrincipals(SSOUser.class);
                Iterator i = principals.iterator();
                while (i.hasNext()) {
                    ssoUser = (SSOUser) i.next();
                    break;
                }

                // Make the cache aware of the user-session association so that
                // it can handle correctly cache entry lookups.
                //_cachePolicy.attachSessionToUser(principal, ssoUser);

                // Instead of associating the Principal used for authenticating (which is a
                // session id), sets the authenticated principal to the SSOUser part of the
                // Subject returned by the Gateway.
                JBossSecurityAssociationActions.setPrincipalInfo(ssoUser, passwordChars, subject);

                // Get the CallerPrincipal mapping
                RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
                Principal oldPrincipal = ssoUser;
                principal = realmMapping.getPrincipal(oldPrincipal);
                logger.debug("Mapped from input principal: " + oldPrincipal + "to: " + principal);
                if (principal.equals(oldPrincipal) == false) {
                    _userPrincipalMap.put(principal, oldPrincipal);
                }

            } else {
                principal = null;
                logger.debug("User: " + username + " is NOT authenticated");
            }
        } catch (NamingException e) {
            principal = null;
            logger.error("Error during authenticate", e);
        }
        logger.debug("End authenticate, principal=" + ssoUser);
        return ssoUser;
    }

    /**
     * Return <code>true</code> if the specified Principal has the specified
     * security role, within the context of this Realm; otherwise return
     * <code>false</code>.
     *
     * Since the Principal, in the JaasSecurityManager, has been stored in its cache
     * using the JOSSO Single Sign-On Session Identifier Principal (see isValid method),
     * when roles are checked , the Principal to be submitted to the overriden
     * operation is not the user principal but the JOSSO Session Id Principal.
     *
     * @param principal Principal for whom the role is to be checked
     * @param role Security role to be checked
     */
    public boolean hasRole(Principal principal, String role) {
        boolean hasRole = false;

        try {
            Context securityCtx = null;
            securityCtx = prepareENC();

            if (securityCtx == null) {
                logger.error("No security context for authenticate(String, String)");
                return false;
            }

            // Get the JBoss security manager from the ENC context
            SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
            if (!isSSODomain(securityMgr.getSecurityDomain())) {
                // This is not a SSO Security domain, let JBoss realm handle this ...
                return super.hasRole(principal, role);
            }

            Subject activeSubject = securityMgr.getActiveSubject();

            logger.debug("Authenticated Subject: " + activeSubject);

            CatalinaSSOUser ssoUser = CatalinaSSOUser.newInstance(this, activeSubject);

            hasRole = super.hasRole(ssoUser, role);
        } catch (NamingException e) {
            principal = null;
            logger.error("Error during authenticate", e);
        }

        return hasRole;
    }

    /**
     * Return the Principal associated with the specified chain of X509
     * client certificates.  If there is none, return <code>null</code>.
     *
     * Before invoking the overriden operation it creates the security JNDI context
     * in case one was not found.
     *
     * @param certs Array of client certificates, with the first one in
     * the array being the certificate of the client itself.
     */
    public Principal authenticate(X509Certificate[] certs) {
        logger.debug("authenticate(X509Certificate[]), Begin");

        try {
            prepareENC();
            return super.authenticate(certs);
        } catch (NamingException ne) {
            // Error creating ENC Context
            logger.error("Cannot create ENC Context");
        }

        logger.debug("authenticate(), Emd");
        return null;
    }

    /** This creates a java:comp/env/security context that contains a
     securityMgr binding pointing to an AuthenticationManager implementation
     and a realmMapping binding pointing to a RealmMapping implementation.
     */
    protected Context prepareENC() throws NamingException {

        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        InitialContext iniCtx = new InitialContext();

        boolean securityContextExists = false;
        boolean isJaasSecurityManager = false;
        try {
            Context envCtx = (Context) iniCtx.lookup("java:comp/env");
            Context securityCtx = (Context) envCtx.lookup("security");
            securityContextExists = true;

            AuthenticationManager securityMgr = (AuthenticationManager) securityCtx.lookup("securityMgr");

            // If the Security Manager set in the web application ENC is not
            // a JaasSecurityManager, unbind the Security context and rebind it
            // with the JaasSecurityManager associated with the JOSSO Security Domain.
            // Note: the jboss-web.xml file of the partner application MUST not have an
            // entry referring to a security domain.
            if (!(securityMgr instanceof JaasSecurityManager)) {
                Util.unbind(envCtx, "security");
            } else
                isJaasSecurityManager = true;
        } catch (NamingException e) {
            // No Security Context found
        }

        // If we do not have a SecurityContext create it
        Context envCtx = null;
        if (!securityContextExists) {
            Thread currentThread = Thread.currentThread();
            logger.debug("Creating ENC using ClassLoader: " + loader);
            ClassLoader parent = loader.getParent();
            while (parent != null) {
                logger.debug(".." + parent);
                parent = parent.getParent();
            }

            envCtx = (Context) iniCtx.lookup("java:comp");
            envCtx = envCtx.createSubcontext("env");
        } else
            envCtx = (Context) iniCtx.lookup("java:comp/env");

        // If the Security Manager binded is not a JaasSecurityManager, rebind using
        // the Security Manager associated with the JOSSO Security Domain.
        if (!isJaasSecurityManager) {
            // Prepare the Security JNDI subcontext
            logger.debug("Linking security/securityMgr to JNDI name: " + JOSSO_SECURITY_DOMAIN);
            Util.bind(envCtx, "security/securityMgr", new LinkRef(JOSSO_SECURITY_DOMAIN));
            Util.bind(envCtx, "security/realmMapping", new LinkRef(JOSSO_SECURITY_DOMAIN));
            Util.bind(envCtx, "security/security-domain", new LinkRef(JOSSO_SECURITY_DOMAIN));
            Util.bind(envCtx, "security/subject", new LinkRef(JOSSO_SECURITY_DOMAIN + "/subject"));
        }

        logger.debug("JBossCatalinaRealm.prepareENC, End");

        return (Context) iniCtx.lookup("java:comp/env/security");
    }

    /** Lookup the authentication CachePolicy object for a security domain. This
     method first treats the cacheJndiName as a ObjectFactory location that is
     capable of returning CachePolicy instances on a per security domain basis
     by appending a '/security-domain-name' string to the cacheJndiName when
     looking up the CachePolicy for a domain. If this fails then the cacheJndiName
     location is treated as a single CachePolicy for all security domains.
     @deprecated No longer used for JBoss 3.2.6 support
     */
    private static CachePolicy lookupCachePolicy(String securityDomain) {
        CachePolicy authCache = null;
        String domainCachePath = cacheJndiName + '/' + securityDomain;
        try {
            InitialContext iniCtx = new InitialContext();
            authCache = (CachePolicy) iniCtx.lookup(domainCachePath);
        } catch (Exception e) {
            // Failed, treat the cacheJndiName name as a global CachePolicy binding
            try {
                InitialContext iniCtx = new InitialContext();
                authCache = (CachePolicy) iniCtx.lookup(cacheJndiName);
            } catch (Exception e2) {
                logger.warn("Failed to locate auth CachePolicy at: " + cacheJndiName + " for securityDomain="
                        + securityDomain);
            }
        }
        return authCache;
    }

    /** Use reflection to attempt to set the authentication cache on the
     * securityMgr argument.
     *
     * This is done this way to avoid dependency with JaasSecurityManager.
     *
     * @deprecated No longer used for JBoss 3.2.6 support
     * @param securityMgr the security manager
     * @param cachePolicy the cache policy implementation
     */
    private static void setSecurityDomainCache(AuthenticationManager securityMgr, CachePolicy cachePolicy) {
        try {
            Class[] setCachePolicyTypes = { CachePolicy.class };
            Method m = securityMgr.getClass().getMethod("setCachePolicy", setCachePolicyTypes);
            Object[] setCachePolicyArgs = { cachePolicy };
            m.invoke(securityMgr, setCachePolicyArgs);
            logger.debug("setCachePolicy, c=" + setCachePolicyArgs[0]);
        } catch (Exception e2) { // No cache policy support, this is ok
            logger.debug("setCachePolicy failed", e2);
        }
    }

}