fr.mby.saml2.sp.opensaml.core.OpenSaml20SpProcessor.java Source code

Java tutorial

Introduction

Here is the source code for fr.mby.saml2.sp.opensaml.core.OpenSaml20SpProcessor.java

Source

/**
 * Copyright (C) 2012 RECIA http://www.recia.fr
 * @Author (C) 2012 Maxime Bossard <mxbossard@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * 
 */

package fr.mby.saml2.sp.opensaml.core;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.opensaml.common.SAMLObject;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.RequestAbstractType;
import org.opensaml.saml2.core.StatusResponseType;
import org.opensaml.saml2.encryption.Decrypter;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.encryption.InlineEncryptedKeyResolver;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.BasicCredential;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.impl.SignatureBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import fr.mby.saml2.sp.api.config.IIdpConfig;
import fr.mby.saml2.sp.api.config.ISpConfig;
import fr.mby.saml2.sp.api.core.ISaml20IdpConnector;
import fr.mby.saml2.sp.api.core.ISaml20SpProcessor;
import fr.mby.saml2.sp.api.core.ISaml20Storage;
import fr.mby.saml2.sp.api.exception.SamlProcessingException;
import fr.mby.saml2.sp.api.exception.SamlSecurityException;
import fr.mby.saml2.sp.api.exception.UnsupportedSamlOperation;
import fr.mby.saml2.sp.api.handler.IAuthenticationHandler;
import fr.mby.saml2.sp.api.handler.ISingleLogoutHandler;
import fr.mby.saml2.sp.api.om.IAuthentication;
import fr.mby.saml2.sp.api.om.IIncomingSaml;
import fr.mby.saml2.sp.api.om.IRequestWaitingForResponse;
import fr.mby.saml2.sp.api.query.IQuery;
import fr.mby.saml2.sp.api.query.engine.IQueryProcessor;
import fr.mby.saml2.sp.api.query.engine.IQueryProcessorFactory;
import fr.mby.saml2.sp.impl.helper.SamlHelper;
import fr.mby.saml2.sp.impl.query.QueryAuthnResponse;

/**
 * Basic OpenSaml impl.
 * 
 * @author GIP RECIA 2012 - Maxime BOSSARD.
 * 
 */
@Service
public class OpenSaml20SpProcessor implements ISaml20SpProcessor, InitializingBean {

    /** Logger. */
    private final Logger logger = LoggerFactory.getLogger(OpenSaml20SpProcessor.class);

    /** SP Configuration. */
    private ISpConfig spConfig;

    /** Decrypt responses encrypted assertions with spCertificate. */
    private Decrypter decrypter;

    /** Signature builder. */
    private final SignatureBuilder signatureBuilder = new SignatureBuilder();

    /** SP Signing credentials (spSigningKey + spSigningCertificate). */
    private Credential spSigningCredential;

    /** IdP connectors. */
    private Collection<ISaml20IdpConnector> idpConnectors;

    /** Map of IdP connectors indexed by their entity Id. */
    private final Map<String, ISaml20IdpConnector> idpConnectorsByEntityId = new HashMap<String, ISaml20IdpConnector>();

    /** SAML 2.0 Storage. */
    private ISaml20Storage saml20Storage;

    /** Authentication handler. */
    private IAuthenticationHandler authenticationHandler;

    /** SLO handler. */
    private ISingleLogoutHandler singleLogoutHandler;

    /** Query Processor Factory. */
    private IQueryProcessorFactory queryProcessorFactory;

    @Override
    public IIncomingSaml processSaml20IncomingRequest(final HttpServletRequest request)
            throws SamlProcessingException, UnsupportedSamlOperation {
        IIncomingSaml incomingSaml = null;

        // Build the adapted query processor
        final IQueryProcessor queryProcessor = this.queryProcessorFactory.buildQueryProcessor(this, request);

        // Process the message
        incomingSaml = queryProcessor.processIncomingSamlMessage();

        this.tryAuthenticationPropagation(incomingSaml);

        return incomingSaml;
    }

    @Override
    public ISaml20IdpConnector findSaml20IdpConnectorToUse(final String idpEntityId) {
        return this.idpConnectorsByEntityId.get(idpEntityId);
    }

    @Override
    public ISaml20Storage getSaml20Storage() {
        return this.saml20Storage;
    }

    @Override
    public Decrypter getDecrypter() {
        return this.decrypter;
    }

    @Override
    public ISpConfig getSpConfig() {
        return this.spConfig;
    }

    @Override
    public Signature signSamlObject(final SignableSAMLObject signable) {
        final Signature newSignature = this.buildSignature(true);

        signable.setSignature(newSignature);
        return newSignature;
    }

    @Override
    public void logout(final String sessionIndex) {
        if (this.singleLogoutHandler != null) {
            this.singleLogoutHandler.logout(sessionIndex);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.saml20Storage, "The SAML 2.0 Storage wasn't injected !");
        Assert.notNull(this.getSpConfig(), "No SP configuration provided for this SP processor !");
        Assert.notNull(this.queryProcessorFactory, "No QueryProcessorFactory injected !");

        // Retrieve IdP connectors and
        // Register this SP processor in the IdP connectors
        Assert.notEmpty(this.idpConnectors, "No IdP connector injected in the SP processor !");
        for (final ISaml20IdpConnector idpConnector : this.idpConnectors) {
            try {
                idpConnector.registerSaml20SpProcessor(this);
                final IIdpConfig idpConfig = idpConnector.getIdpConfig();
                if (idpConfig != null) {
                    this.idpConnectorsByEntityId.put(idpConfig.getIdpEntityId(), idpConnector);
                } else {
                    this.logger.warn(
                            "No IdP config found while registering an IdPConnector in SPProcessor with id: [{}] !",
                            this.getSpConfig().getId());
                }
            } catch (final IllegalAccessError e) {
                // Catch exception thrown by fake IdPs like CAS Fake IdP.
            }
        }

        this.spSigningCredential = SecurityHelper.getSimpleCredential(
                this.getSpConfig().getSigningCredential().getEntityCertificate(),
                this.getSpConfig().getSigningKey());
        Assert.notNull(this.spSigningCredential,
                "Unable to build SP signing credentials (signing public + private keys) !");

        this.decrypter = this.buildDecrypter();

        // Register this processor in the Helper
        SamlHelper.registerSpProcessor(this);

        if (this.authenticationHandler == null) {
            this.logger.warn("No Authentication Handler configured !");
        }

        if (this.singleLogoutHandler == null) {
            this.logger.warn("No Single Logout Handler configured !");
        }
    }

    /**
     * Check if the Response correspond to a request already present in request cache ! Remove the Request Data from
     * request cache.
     * 
     * @param response
     * @return the original request cannot be null
     * @throws SamlSecurityException
     */
    protected IRequestWaitingForResponse checkInResponseToRequest(final String inResponseToId)
            throws SamlSecurityException {
        IRequestWaitingForResponse request = null;

        if (StringUtils.hasText(inResponseToId)) {
            // Get request from cache
            request = this.saml20Storage.removeRequestWaitingForResponse(inResponseToId);
        }

        if (request == null) {
            throw new SamlSecurityException("Response reference a Request which is not (anymore ?) in cache !");
        }

        return request;
    }

    /**
     * Find the SAML 2.0 IdP Connector to use to process the SAML Object.
     * 
     * @param samlObject
     *            the SAML 2.0 object to process
     * @return the SAML 2.0 IdP connector attached
     * @throws SamlProcessingException
     *             if no IdP connector found
     */
    protected ISaml20IdpConnector findSaml20IdpConnectorToUse(final SAMLObject samlObject)
            throws SamlProcessingException {
        ISaml20IdpConnector samlConnector = null;

        Assert.notNull(samlObject, "No signable SAML objet provided !");

        if (StatusResponseType.class.isAssignableFrom(samlObject.getClass())) {
            // The SAML object is a Response, so the original request must be in the cache !
            final StatusResponseType samlResponse = (StatusResponseType) samlObject;
            final String originalRequestId = samlResponse.getInResponseTo();

            if (StringUtils.hasText(originalRequestId)) {
                final IRequestWaitingForResponse originalRequestData = this.saml20Storage
                        .findRequestWaitingForResponse(originalRequestId);
                if (originalRequestData != null) {
                    samlConnector = originalRequestData.getIdpConnectorBuilder();
                }
            }

        } else if (RequestAbstractType.class.isAssignableFrom(samlObject.getClass())) {
            // Search IdPConnector by Issuer
            final RequestAbstractType samlRequest = (RequestAbstractType) samlObject;

            final Issuer issuer = samlRequest.getIssuer();
            if (issuer != null) {
                final String issuerEntityId = issuer.getValue();
                samlConnector = this.idpConnectorsByEntityId.get(issuerEntityId);
            }

        }

        if (samlConnector == null) {
            throw new SamlProcessingException("Unable to find an IdP Connector to process the SAML request !");
        }

        return samlConnector;
    }

    public void setSpConfig(final ISpConfig spConfig) {
        this.spConfig = spConfig;
    }

    /**
     * Build a decrypter if a private key was provided.
     * 
     * @return the decrypter
     */
    protected Decrypter buildDecrypter() {
        Decrypter decrypter = null;

        final BasicCredential credential = new BasicCredential();
        credential.setPrivateKey(this.getSpConfig().getDecryptionKey());
        decrypter = new Decrypter(null, new StaticKeyInfoCredentialResolver(credential),
                new InlineEncryptedKeyResolver());

        return decrypter;
    }

    /**
     * Build a SAML2 signature with signing credentials.
     * 
     * @return the SAML2 signature.
     */
    protected Signature buildSignature(final boolean withoutKeyInfo) {
        Signature signature = this.signatureBuilder.buildObject();

        try {
            SecurityHelper.prepareSignatureParams(signature, this.spSigningCredential,
                    Configuration.getGlobalSecurityConfiguration(), null);
            signature.setSigningCredential(this.spSigningCredential);

            // FIX MBD: Remove key info which is optional to save request length
            if (withoutKeyInfo) {
                signature.setKeyInfo(null);
            }

        } catch (final SecurityException e) {
            this.logger.error("Error while building signature !", e);
            signature = null;
        }

        return signature;
    }

    /**
     * Try to propagate authentications to Authentication Handler.
     * 
     * @param incomingSaml
     *            an incoming saml message
     */
    protected void tryAuthenticationPropagation(final IIncomingSaml incomingSaml) {
        if (incomingSaml != null) {
            final IQuery samlQuery = incomingSaml.getSamlQuery();

            // Propagate authentication to authentication handlers
            if (samlQuery != null && QueryAuthnResponse.class.isAssignableFrom(samlQuery.getClass())) {
                final QueryAuthnResponse queryAuthnResponse = (QueryAuthnResponse) samlQuery;
                if (this.authenticationHandler != null) {
                    final List<IAuthentication> authns = queryAuthnResponse.getSamlAuthentications();
                    this.authenticationHandler.propagateAuthentications(authns);
                }
            }

        }
    }

    public Collection<ISaml20IdpConnector> getIdpConnectors() {
        return this.idpConnectors;
    }

    public void setIdpConnectors(final Collection<ISaml20IdpConnector> idpConnectors) {
        this.idpConnectors = idpConnectors;
    }

    /**
     * Setter of samlStorage.
     * 
     * @param samlStorage
     *            the samlStorage to set
     */
    public void setSaml20Storage(final ISaml20Storage samlStorage) {
        this.saml20Storage = samlStorage;
    }

    /**
     * Getter of authenticationHandler.
     * 
     * @return the authenticationHandler
     */
    public IAuthenticationHandler getAuthenticationHandler() {
        return this.authenticationHandler;
    }

    /**
     * Setter of authenticationHandler.
     * 
     * @param authenticationHandler
     *            the authenticationHandler to set
     */
    public void setAuthenticationHandler(final IAuthenticationHandler authenticationHandler) {
        this.authenticationHandler = authenticationHandler;
    }

    /**
     * Getter of singleLogoutHandler.
     * 
     * @return the singleLogoutHandler
     */
    public ISingleLogoutHandler getSingleLogoutHandler() {
        return this.singleLogoutHandler;
    }

    /**
     * Setter of singleLogoutHandler.
     * 
     * @param singleLogoutHandler
     *            the singleLogoutHandler to set
     */
    public void setSingleLogoutHandler(final ISingleLogoutHandler singleLogoutHandler) {
        this.singleLogoutHandler = singleLogoutHandler;
    }

    /**
     * Getter of queryProcessorFactory.
     * 
     * @return the queryProcessorFactory
     */
    public IQueryProcessorFactory getQueryProcessorFactory() {
        return this.queryProcessorFactory;
    }

    /**
     * Setter of queryProcessorFactory.
     * 
     * @param queryProcessorFactory
     *            the queryProcessorFactory to set
     */
    public void setQueryProcessorFactory(final IQueryProcessorFactory queryProcessorFactory) {
        this.queryProcessorFactory = queryProcessorFactory;
    }

}