Package org.picketlink.identity.federation.bindings.tomcat.sp

Source Code of org.picketlink.identity.federation.bindings.tomcat.sp.BaseFormAuthenticator

/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, 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.picketlink.identity.federation.bindings.tomcat.sp;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.crypto.dsig.CanonicalizationMethod;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Session;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.picketlink.identity.federation.PicketLinkLogger;
import org.picketlink.identity.federation.PicketLinkLoggerFactory;
import org.picketlink.identity.federation.api.saml.v2.metadata.MetaDataExtractor;
import org.picketlink.identity.federation.core.ErrorCodes;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditHelper;
import org.picketlink.identity.federation.core.config.PicketLinkType;
import org.picketlink.identity.federation.core.config.SPType;
import org.picketlink.identity.federation.core.exceptions.ConfigurationException;
import org.picketlink.identity.federation.core.exceptions.ParsingException;
import org.picketlink.identity.federation.core.exceptions.ProcessingException;
import org.picketlink.identity.federation.core.handler.config.Handlers;
import org.picketlink.identity.federation.core.interfaces.TrustKeyManager;
import org.picketlink.identity.federation.core.parsers.saml.SAMLParser;
import org.picketlink.identity.federation.core.saml.v2.constants.JBossSAMLURIConstants;
import org.picketlink.identity.federation.core.saml.v2.factories.SAML2HandlerChainFactory;
import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChainConfig;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerChain;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerChainConfig;
import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil;
import org.picketlink.identity.federation.core.saml.v2.util.HandlerUtil;
import org.picketlink.identity.federation.core.util.CoreConfigUtil;
import org.picketlink.identity.federation.core.util.StringUtil;
import org.picketlink.identity.federation.core.util.SystemPropertiesUtil;
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
import org.picketlink.identity.federation.saml.v2.metadata.EndpointType;
import org.picketlink.identity.federation.saml.v2.metadata.EntitiesDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.EntityDescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.IDPSSODescriptorType;
import org.picketlink.identity.federation.saml.v2.metadata.KeyDescriptorType;
import org.picketlink.identity.federation.web.config.AbstractSAMLConfigurationProvider;
import org.picketlink.identity.federation.web.constants.GeneralConstants;
import org.picketlink.identity.federation.web.util.ConfigurationUtil;
import org.picketlink.identity.federation.web.util.SAMLConfigurationProvider;
import org.w3c.dom.Document;

/**
* Base Class for Service Provider Form Authenticators
*
* @author Anil.Saldhana@redhat.com
* @since Jun 9, 2009
*/
public abstract class BaseFormAuthenticator extends FormAuthenticator {
   
    protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
   
    protected boolean enableAudit = false;
    protected PicketLinkAuditHelper auditHelper = null;

    protected TrustKeyManager keyManager;

    protected SPType spConfiguration = null;

    protected PicketLinkType picketLinkConfiguration = null;

    protected String serviceURL = null;

    protected String identityURL = null;

    protected String issuerID = null;

    protected String configFile = GeneralConstants.CONFIG_FILE_LOCATION;

    /**
     * If the service provider is configured with an IDP metadata file, then this certificate can be picked up from the metadata
     */
    protected transient X509Certificate idpCertificate = null;

    protected transient SAML2HandlerChain chain = null;

    protected transient String samlHandlerChainClass = null;

    protected Map<String, Object> chainConfigOptions = new HashMap<String, Object>();

    // Whether the authenticator has to to save and restore request
    protected boolean saveRestoreRequest = true;

    /**
     * A Lock for Handler operations in the chain
     */
    protected Lock chainLock = new ReentrantLock();

    protected String canonicalizationMethod = CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS;

    /**
     * The user can inject a fully qualified name of a {@link SAMLConfigurationProvider}
     */
    protected SAMLConfigurationProvider configProvider = null;

    /**
     * Servlet3 related changes forced Tomcat to change the authenticate method signature in the FormAuthenticator. For now, we
     * use reflection for forward compatibility. This has to be changed in future.
     */
    private Method theSuperRegisterMethod = null;

    /**
     * If it is determined that we are running in a Tomcat6/JBAS5 environment, there is no need to seek the super.register
     * method that conforms to the servlet3 spec changes
     */
    private boolean seekSuperRegisterMethod = true;

    public BaseFormAuthenticator() {
        super();
    }

    protected String idpAddress = null;

    /**
     * If the request.getRemoteAddr is not exactly the IDP address that you have keyed in your deployment descriptor for
     * keystore alias, you can set it here explicitly
     */
    public void setIdpAddress(String idpAddress) {
        this.idpAddress = idpAddress;
    }

    /**
     * Get the name of the configuration file
     * @return
     */
    public String getConfigFile() {
        return configFile;
    }

    /**
     * Set the name of the configuration file
     * @param configFile
     */
    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }

    /**
     * Set the SAML Handler Chain Class fqn
     * @param samlHandlerChainClass
     */
    public void setSamlHandlerChainClass(String samlHandlerChainClass) {
        this.samlHandlerChainClass = samlHandlerChainClass;
    }

    /**
     * Set the service URL
     * @param serviceURL
     */
    public void setServiceURL(String serviceURL) {
        this.serviceURL = serviceURL;
    }

    /**
     * Set whether the authenticator saves/restores the request
     * during form authentication
     * @param saveRestoreRequest
     */
    public void setSaveRestoreRequest(boolean saveRestoreRequest) {
        this.saveRestoreRequest = saveRestoreRequest;
    }

    /**
     * Set the {@link SAMLConfigurationProvider} fqn
     * @param cp fqn of a {@link SAMLConfigurationProvider}
     */
    public void setConfigProvider(String cp) {
        if (cp == null)
            throw new IllegalStateException(ErrorCodes.NULL_ARGUMENT + cp);
        Class<?> clazz = SecurityActions.loadClass(getClass(), cp);
        if (clazz == null)
            throw new RuntimeException(ErrorCodes.CLASS_NOT_LOADED + cp);
        try {
            configProvider = (SAMLConfigurationProvider) clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(ErrorCodes.CANNOT_CREATE_INSTANCE + cp + ":" + e.getMessage());
        }
    }
   
    /**
     * Set an instance of the {@link SAMLConfigurationProvider}
     * @param configProvider
     */
    public void setConfigProvider(SAMLConfigurationProvider configProvider) {
        this.configProvider = configProvider;
    }

    /**
     * Get the {@link SPType}
     * @return
     */
    public SPType getConfiguration() {
        return spConfiguration;
    }

    /**
     * Set a separate issuer id
     *
     * @param issuerID
     */
    public void setIssuerID(String issuerID) {
        this.issuerID = issuerID;
    }

    /**
     * Set the logout page
     * @param logOutPage
     */
    public void setLogOutPage(String logOutPage) {
        logger.warn("Option logOutPage is now configured with the PicketLinkSP element.");

    }

    /**
     * Perform validation os the request object
     *
     * @param request
     * @return
     * @throws IOException
     * @throws GeneralSecurityException
     */
    protected boolean validate(Request request) {
        return request.getParameter("SAMLResponse") != null;
    }

    /**
     * Get the Identity URL
     *
     * @return
     */
    public String getIdentityURL() {
        return identityURL;
    }

    /**
     * Get the {@link X509Certificate} of the IDP if provided via the IDP metadata file
     *
     * @return {@link X509Certificate} or null
     */
    public X509Certificate getIdpCertificate() {
        return idpCertificate;
    }

    /**
     * This method is a hack!!! Tomcat on account of Servlet3 changed their authenticator method signatures We utilize Java
     * Reflection to identify the super register method on the first call and save it. Subsquent invocations utilize the saved
     * {@link Method}
     *
     * @see org.apache.catalina.authenticator.AuthenticatorBase#register(org.apache.catalina.connector.Request,
     *      org.apache.catalina.connector.Response, java.security.Principal, java.lang.String, java.lang.String,
     *      java.lang.String)
     */
    @Override
    protected void register(Request request, Response response, Principal principal, String arg3, String arg4, String arg5) {
        // Try the JBossAS6 version
        if (theSuperRegisterMethod == null && seekSuperRegisterMethod) {
            Class<?>[] args = new Class[] { Request.class, HttpServletResponse.class, Principal.class, String.class,
                    String.class, String.class };
            Class<?> superClass = getAuthenticatorBaseClass();
            theSuperRegisterMethod = SecurityActions.getMethod(superClass, "register", args);
        }
        try {
            if (theSuperRegisterMethod != null) {
                Object[] callArgs = new Object[] { request, response, principal, arg3, arg4, arg5 };
                theSuperRegisterMethod.invoke(this, callArgs);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // Try the older version
        if (theSuperRegisterMethod == null) {
            seekSuperRegisterMethod = false; // Don't try to seek super register method on next invocation
            super.register(request, response, principal, arg3, arg4, arg5);
            return;
        }
    }

    /**
     * Fall back on local authentication at the service provider side
     *
     * @param request
     * @param response
     * @param loginConfig
     * @return
     * @throws IOException
     */
    protected boolean localAuthentication(Request request, Response response, LoginConfig loginConfig) throws IOException {
        if (request.getUserPrincipal() == null) {
            logger.samlSPFallingBackToLocalFormAuthentication();// fallback
            try {
                return super.authenticate(request, response, loginConfig);
            } catch (NoSuchMethodError e) {
                // Use Reflection
                try {
                    Method method = super.getClass().getMethod("authenticate",
                            new Class[] { HttpServletRequest.class, HttpServletResponse.class, LoginConfig.class });
                    return (Boolean) method.invoke(this, new Object[] { request.getRequest(), response.getResponse(),
                            loginConfig });
                } catch (Exception ex) {
                    throw logger.unableLocalAuthentication(ex);
                }
            }
        } else
            return true;
    }

    /**
     * Return the SAML Binding that this authenticator supports
     *
     * @see {@link JBossSAMLURIConstants#SAML_HTTP_POST_BINDING}
     * @see {@link JBossSAMLURIConstants#SAML_HTTP_REDIRECT_BINDING}
     * @return
     */
    protected abstract String getBinding();

    /**
     * Attempt to process a metadata file available locally
     */
    protected void processIDPMetadataFile(String idpMetadataFile) {
        ServletContext servletContext = context.getServletContext();
        InputStream is = servletContext.getResourceAsStream(idpMetadataFile);
        if (is == null)
            return;

        Object metadata = null;
        try {
            Document samlDocument = DocumentUtil.getDocument(is);
            SAMLParser parser = new SAMLParser();
            metadata = parser.parse(DocumentUtil.getNodeAsStream(samlDocument));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        IDPSSODescriptorType idpSSO = null;
        if (metadata instanceof EntitiesDescriptorType) {
            EntitiesDescriptorType entities = (EntitiesDescriptorType) metadata;
            idpSSO = handleMetadata(entities);
        } else {
            idpSSO = handleMetadata((EntityDescriptorType) metadata);
        }
        if (idpSSO == null) {
            logger.samlSPUnableToGetIDPDescriptorFromMetadata();
            return;
        }
        List<EndpointType> endpoints = idpSSO.getSingleSignOnService();
        for (EndpointType endpoint : endpoints) {
            String endpointBinding = endpoint.getBinding().toString();
            if (endpointBinding.contains("HTTP-POST"))
                endpointBinding = "POST";
            else if (endpointBinding.contains("HTTP-Redirect"))
                endpointBinding = "REDIRECT";
            if (getBinding().equals(endpointBinding)) {
                identityURL = endpoint.getLocation().toString();
                break;
            }
        }
        List<KeyDescriptorType> keyDescriptors = idpSSO.getKeyDescriptor();
        if (keyDescriptors.size() > 0) {
            this.idpCertificate = MetaDataExtractor.getCertificate(keyDescriptors.get(0));
        }
    }

    /**
     * Process the configuration from the configuration file
     */
    @SuppressWarnings("deprecation")
    protected void processConfiguration() {
        ServletContext servletContext = context.getServletContext();
        InputStream is = servletContext.getResourceAsStream(configFile);

        try {
            // Work on the IDP Configuration
            if (configProvider != null) {
                try {
                    if (is == null) {
                        // Try the older version
                        is = servletContext.getResourceAsStream(GeneralConstants.DEPRECATED_CONFIG_FILE_LOCATION);
                       
                        // Additionally parse the deprecated config file
                        if (is != null && configProvider instanceof AbstractSAMLConfigurationProvider) {
                            ((AbstractSAMLConfigurationProvider) configProvider).setConfigFile(is);
                        }
                    } else {
                        // Additionally parse the consolidated config file
                        if (is != null && configProvider instanceof AbstractSAMLConfigurationProvider) {
                            ((AbstractSAMLConfigurationProvider) configProvider).setConsolidatedConfigFile(is);
                        }
                    }

                    picketLinkConfiguration = configProvider.getPicketLinkConfiguration();
                    spConfiguration = configProvider.getSPConfiguration();
                } catch (ProcessingException e) {
                    throw logger.samlSPConfigurationError(e);
                } catch (ParsingException e) {
                    throw logger.samlSPConfigurationError(e);
                }
            } else {
                if (is != null) {
                    try {
                        picketLinkConfiguration = ConfigurationUtil.getConfiguration(is);
                        spConfiguration = (SPType) picketLinkConfiguration.getIdpOrSP();
                    } catch (ParsingException e) {
                        logger.trace(e);
                        throw logger.samlSPConfigurationError(e);
                    }
                } else {
                    is = servletContext.getResourceAsStream(GeneralConstants.DEPRECATED_CONFIG_FILE_LOCATION);
                    if (is == null)
                        throw logger.configurationFileMissing(configFile);
                    spConfiguration = ConfigurationUtil.getSPConfiguration(is);
                }
            }
           
            if (this.picketLinkConfiguration != null) {
                enableAudit = picketLinkConfiguration.isEnableAudit();

                //See if we have the system property enabled
                if(!enableAudit){
                    String sysProp = SecurityActions.getSystemProperty(GeneralConstants.AUDIT_ENABLE, "NULL");
                    if(!"NULL".equals(sysProp)){
                        enableAudit = Boolean.parseBoolean(sysProp);  
                    }
                }

                if (enableAudit) {
                    if (auditHelper == null) {
                        String securityDomainName = PicketLinkAuditHelper.getSecurityDomainName(servletContext);
                       
                        auditHelper = new PicketLinkAuditHelper(securityDomainName);
                    }
                }
            }

            if (StringUtil.isNotNull(spConfiguration.getIdpMetadataFile())) {
                processIDPMetadataFile(spConfiguration.getIdpMetadataFile());
            } else {
                this.identityURL = spConfiguration.getIdentityURL();
            }
            this.serviceURL = spConfiguration.getServiceURL();
            this.canonicalizationMethod = spConfiguration.getCanonicalizationMethod();

            logger.samlSPSettingCanonicalizationMethod(canonicalizationMethod);
            XMLSignatureUtil.setCanonicalizationMethodType(canonicalizationMethod);

            logger.trace("Identity Provider URL=" + this.identityURL);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected IDPSSODescriptorType handleMetadata(EntitiesDescriptorType entities) {
        IDPSSODescriptorType idpSSO = null;

        List<Object> entityDescs = entities.getEntityDescriptor();
        for (Object entityDescriptor : entityDescs) {
            if (entityDescriptor instanceof EntitiesDescriptorType) {
                idpSSO = getIDPSSODescriptor(entities);
            } else
                idpSSO = handleMetadata((EntityDescriptorType) entityDescriptor);
            if (idpSSO != null)
                break;
        }
        return idpSSO;
    }

    protected IDPSSODescriptorType handleMetadata(EntityDescriptorType entityDescriptor) {
        return CoreConfigUtil.getIDPDescriptor(entityDescriptor);
    }

    protected IDPSSODescriptorType getIDPSSODescriptor(EntitiesDescriptorType entities) {
        List<Object> entityDescs = entities.getEntityDescriptor();
        for (Object entityDescriptor : entityDescs) {

            if (entityDescriptor instanceof EntitiesDescriptorType) {
                return getIDPSSODescriptor((EntitiesDescriptorType) entityDescriptor);
            }
            return CoreConfigUtil.getIDPDescriptor((EntityDescriptorType) entityDescriptor);
        }
        return null;
    }

    protected void initializeHandlerChain() throws ConfigurationException, ProcessingException {
        populateChainConfig();
        SAML2HandlerChainConfig handlerChainConfig = new DefaultSAML2HandlerChainConfig(chainConfigOptions);

        Set<SAML2Handler> samlHandlers = chain.handlers();

        for (SAML2Handler handler : samlHandlers) {
            handler.initChainConfig(handlerChainConfig);
        }
    }

    protected void populateChainConfig() throws ConfigurationException, ProcessingException {
        chainConfigOptions.put(GeneralConstants.CONFIGURATION, spConfiguration);
        chainConfigOptions.put(GeneralConstants.ROLE_VALIDATOR_IGNORE, "false"); // No validator as tomcat realm does validn

        if (doSupportSignature()) {
            chainConfigOptions.put(GeneralConstants.KEYPAIR, keyManager.getSigningKeyPair());
            //If there is a need for X509Data in signedinfo
            String certificateAlias = (String)keyManager.getAdditionalOption(GeneralConstants.X509CERTIFICATE);
            if(certificateAlias != null){
                chainConfigOptions.put(GeneralConstants.X509CERTIFICATE, keyManager.getCertificate(certificateAlias));
            }
        }
    }

    protected void sendToLogoutPage(Request request, Response response, Session session) throws IOException, ServletException {
        // we are invalidated.
        RequestDispatcher dispatch = context.getServletContext().getRequestDispatcher(this.getConfiguration().getLogOutPage());
        if (dispatch == null)
            logger.samlSPCouldNotDispatchToLogoutPage(this.getConfiguration().getLogOutPage());
        else {
            logger.trace("Forwarding request to logOutPage: " + this.getConfiguration().getLogOutPage());
            session.expire();
            try {
                dispatch.forward(request, response);
            } catch (Exception e) {
                // JBAS5.1 and 6 quirkiness
                dispatch.forward(request.getRequest(), response);
            }
        }
    }

    // Mock test purpose
    public void testStart() throws LifecycleException {
        this.saveRestoreRequest = false;
        if (context == null)
            throw new RuntimeException("Catalina Context not set up");
        startPicketLink();
    }

    protected void startPicketLink() throws LifecycleException {
        SystemPropertiesUtil.ensure();
        Handlers handlers = null;

        // Get the chain from config
        if (StringUtil.isNullOrEmpty(samlHandlerChainClass)) {
            chain = SAML2HandlerChainFactory.createChain();
        } else {
            try {
                chain = SAML2HandlerChainFactory.createChain(this.samlHandlerChainClass);
            } catch (ProcessingException e1) {
                throw new LifecycleException(e1);
            }
        }

        ServletContext servletContext = context.getServletContext();

        this.processConfiguration();

        try {
            if (picketLinkConfiguration != null) {
                handlers = picketLinkConfiguration.getHandlers();
            } else {
                // Get the handlers
                String handlerConfigFileName = GeneralConstants.HANDLER_CONFIG_FILE_LOCATION;
                handlers = ConfigurationUtil.getHandlers(servletContext.getResourceAsStream(handlerConfigFileName));
            }

            chain.addAll(HandlerUtil.getHandlers(handlers));

            this.initKeyProvider(context);
            this.populateChainConfig();
            this.initializeHandlerChain();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * <p>
     * Indicates if digital signatures/validation of SAML assertions are enabled. Subclasses that supports signature should
     * override this method.
     * </p>
     *
     * @return
     */
    protected boolean doSupportSignature() {
        if (spConfiguration != null) {
            return spConfiguration.isSupportsSignature();
        }
        return false;
    }

    private Class<?> getAuthenticatorBaseClass() {
        Class<?> myClass = getClass();
        do {
            myClass = myClass.getSuperclass();
        } while (myClass != AuthenticatorBase.class);
        return myClass;
    }

    protected abstract void initKeyProvider(Context context) throws LifecycleException;
   
    public void setAuditHelper(PicketLinkAuditHelper auditHelper) {
        this.auditHelper = auditHelper;
    }
}
TOP

Related Classes of org.picketlink.identity.federation.bindings.tomcat.sp.BaseFormAuthenticator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.