be.fedict.eid.idp.protocol.saml2.AbstractSAML2ProtocolService.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.eid.idp.protocol.saml2.AbstractSAML2ProtocolService.java

Source

/*
 * eID Identity Provider Project.
 * Copyright (C) 2010 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * 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, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.eid.idp.protocol.saml2;

import java.net.URL;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.crypto.SecretKey;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.binding.BasicSAMLMessageContext;
import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
import org.opensaml.saml2.binding.decoding.HTTPPostDecoder;
import org.opensaml.saml2.binding.decoding.HTTPRedirectDeflateDecoder;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Response;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.xml.ConfigurationException;

import be.fedict.eid.idp.common.Attribute;
import be.fedict.eid.idp.common.AttributeConstants;
import be.fedict.eid.idp.common.SamlAuthenticationPolicy;
import be.fedict.eid.idp.common.saml2.Saml2Util;
import be.fedict.eid.idp.spi.DefaultAttribute;
import be.fedict.eid.idp.spi.IdentityProviderConfiguration;
import be.fedict.eid.idp.spi.IdentityProviderFlow;
import be.fedict.eid.idp.spi.IdentityProviderProtocolService;
import be.fedict.eid.idp.spi.IncomingRequest;
import be.fedict.eid.idp.spi.ReturnResponse;

/**
 * SAML2 Browser POST Profile protocol service.
 * 
 * @author Frank Cornelis
 */
public abstract class AbstractSAML2ProtocolService implements IdentityProviderProtocolService {

    private static final Log LOG = LogFactory.getLog(AbstractSAML2ProtocolService.class);

    public static final String IDP_CONFIG_CONTEXT_ATTRIBUTE = AbstractSAML2ProtocolService.class.getName()
            + ".IdPConfig";

    public static final String TARGET_URL_SESSION_ATTRIBUTE = AbstractSAML2ProtocolService.class.getName()
            + ".TargetUrl";

    public static final String RELAY_STATE_SESSION_ATTRIBUTE = AbstractSAML2ProtocolService.class.getName()
            + ".RelayState";

    public static final String IN_RESPONSE_TO_SESSION_ATTRIBUTE = AbstractSAML2ProtocolService.class.getName()
            + ".InResponseTo";

    public static final String ISSUER_SESSION_ATTRIBUTE = AbstractSAML2ProtocolService.class.getName() + ".ISSUER";

    public static final String LANGUAGE_PARAM = "language";

    public String getId() {

        LOG.debug("get ID");
        return "SAML2";
    }

    public void init(ServletContext servletContext, IdentityProviderConfiguration configuration) {

        LOG.debug("init");

        setIdPConfiguration(servletContext, configuration);

        try {
            DefaultBootstrap.bootstrap();

        } catch (ConfigurationException e) {
            throw new RuntimeException("OpenSAML configuration error: " + e.getMessage(), e);
        }
    }

    public IncomingRequest handleIncomingRequest(HttpServletRequest request, HttpServletResponse response)
            throws Exception {

        LOG.debug("handling incoming request");

        // get language param if any
        String language = null;
        if (null != request.getParameter(LANGUAGE_PARAM)) {
            language = request.getParameter(LANGUAGE_PARAM);
        }

        SAMLMessageDecoder decoder;
        if (request.getMethod().equals("POST")) {
            decoder = new HTTPPostDecoder();
        } else {
            decoder = new HTTPRedirectDeflateDecoder();
        }

        BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject> messageContext = new BasicSAMLMessageContext<SAMLObject, SAMLObject, SAMLObject>();
        messageContext.setInboundMessageTransport(new HttpServletRequestAdapter(request));

        decoder.decode(messageContext);

        SAMLObject samlObject = messageContext.getInboundSAMLMessage();
        LOG.debug("SAML object class: " + samlObject.getClass().getName());
        if (!(samlObject instanceof AuthnRequest)) {
            throw new IllegalArgumentException("expected a SAML2 AuthnRequest document");
        }
        AuthnRequest authnRequest = (AuthnRequest) samlObject;

        String issuer = authnRequest.getIssuer().getValue();
        if (null == issuer) {
            throw new IllegalArgumentException("SAML2 AuthnRequest " + "does not have an issuer set.");
        }
        LOG.debug("Issuer: " + issuer);
        setIssuer(issuer, request);

        String targetUrl = authnRequest.getAssertionConsumerServiceURL();
        LOG.debug("target URL: " + targetUrl);
        setTargetUrl(targetUrl, request);

        String relayState = messageContext.getRelayState();
        setRelayState(relayState, request);

        String inResponseTo = authnRequest.getID();
        setInResponseTo(inResponseTo, request);

        LOG.debug("request: " + Saml2Util.domToString(Saml2Util.marshall(authnRequest).getOwnerDocument(), true));

        // Signature validation
        X509Certificate certificate = null;
        if (null != authnRequest.getSignature()) {
            // fix for recent versions of Apache xmlsec.
            authnRequest.getDOM().setIdAttribute("ID", true);

            List<X509Certificate> certChain = Saml2Util.validateSignature(authnRequest.getSignature());
            certificate = Saml2Util.getEndCertificate(certChain);
        }

        // HTTP Referer check
        String referer = request.getHeader("referer");
        if (null != authnRequest.getAssertionConsumerServiceURL() && null != referer) {

            URL refererUrl = new URL(referer);
            URL acsUrl = new URL(authnRequest.getAssertionConsumerServiceURL());

            LOG.debug("HTTP Referer check: referer=\"" + refererUrl.getHost() + "\" request.acs=\""
                    + acsUrl.getHost() + "\"");

            if (!refererUrl.getHost().equalsIgnoreCase(acsUrl.getHost())) {
                throw new IllegalArgumentException("Invalid referer!");
            }
        }

        return new IncomingRequest(getAuthenticationFlow(), issuer, certificate,
                Collections.singletonList(language), null);

    }

    public ReturnResponse handleReturnResponse(HttpSession httpSession, String userId,
            Map<String, Attribute> attributes, SecretKey secretKey, PublicKey publicKey, String rpTargetUrl,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        LOG.debug("handle return response");
        LOG.debug("userId: " + userId);
        String targetUrl = rpTargetUrl;
        if (null == targetUrl) {
            targetUrl = getTargetUrl(httpSession);
        }

        IdentityProviderConfiguration configuration = getIdPConfiguration(httpSession.getServletContext());

        String requestIssuer = getIssuer(httpSession);
        String relayState = getRelayState(httpSession);
        String inResponseTo = getInResponseTo(httpSession);

        String issuerName = getResponseIssuer(configuration);

        Response samlResponse = Saml2Util.getResponse(inResponseTo, targetUrl, issuerName);

        // generate assertion
        Assertion assertion = Saml2Util.getAssertion(issuerName, inResponseTo, requestIssuer, targetUrl,
                configuration.getResponseTokenValidity(), samlResponse.getIssueInstant(), getAuthenticationPolicy(),
                userId, attributes, secretKey, publicKey);
        samlResponse.getAssertions().add(assertion);

        return handleSamlResponse(request, targetUrl, samlResponse, relayState);
    }

    public String findAttributeUri(String uri) {

        DefaultAttribute defaultAttribute = DefaultAttribute.findDefaultAttribute(uri);
        if (null != defaultAttribute) {
            switch (defaultAttribute) {

            case LAST_NAME:
                return AttributeConstants.LAST_NAME_CLAIM_TYPE_URI;
            case FIRST_NAME:
                return AttributeConstants.FIRST_NAME_CLAIM_TYPE_URI;
            case NAME:
                return AttributeConstants.NAME_CLAIM_TYPE_URI;
            case IDENTIFIER:
                return AttributeConstants.PPID_CLAIM_TYPE_URI;
            case ADDRESS:
                return AttributeConstants.STREET_ADDRESS_CLAIM_TYPE_URI;
            case LOCALITY:
                return AttributeConstants.LOCALITY_CLAIM_TYPE_URI;
            case POSTAL_CODE:
                return AttributeConstants.POSTAL_CODE_CLAIM_TYPE_URI;
            case GENDER:
                return AttributeConstants.GENDER_CLAIM_TYPE_URI;
            case DATE_OF_BIRTH:
                return AttributeConstants.DATE_OF_BIRTH_CLAIM_TYPE_URI;
            case NATIONALITY:
                return AttributeConstants.NATIONALITY_CLAIM_TYPE_URI;
            case PLACE_OF_BIRTH:
                return AttributeConstants.PLACE_OF_BIRTH_CLAIM_TYPE_URI;
            case PHOTO:
                return AttributeConstants.PHOTO_CLAIM_TYPE_URI;
            case CARD_NUMBER:
                return AttributeConstants.CARD_NUMBER_TYPE_URI;
            case CARD_VALIDITY_BEGIN:
                return AttributeConstants.CARD_VALIDITY_BEGIN_TYPE_URI;
            case CARD_VALIDITY_END:
                return AttributeConstants.CARD_VALIDITY_END_TYPE_URI;
            }
        }

        return null;
    }

    /*
     * Helper methods for state handling
     */

    protected void setTargetUrl(String targetUrl, HttpServletRequest request) {
        HttpSession httpSession = request.getSession();
        httpSession.setAttribute(TARGET_URL_SESSION_ATTRIBUTE, targetUrl);
    }

    protected String getTargetUrl(HttpSession httpSession) {
        return (String) httpSession.getAttribute(TARGET_URL_SESSION_ATTRIBUTE);
    }

    protected void setInResponseTo(String inResponseTo, HttpServletRequest request) {
        HttpSession httpSession = request.getSession();
        httpSession.setAttribute(IN_RESPONSE_TO_SESSION_ATTRIBUTE, inResponseTo);
    }

    protected String getInResponseTo(HttpSession httpSession) {
        return (String) httpSession.getAttribute(IN_RESPONSE_TO_SESSION_ATTRIBUTE);
    }

    protected void setRelayState(String relayState, HttpServletRequest request) {
        HttpSession httpSession = request.getSession();
        httpSession.setAttribute(RELAY_STATE_SESSION_ATTRIBUTE, relayState);
    }

    protected String getRelayState(HttpSession httpSession) {
        return (String) httpSession.getAttribute(RELAY_STATE_SESSION_ATTRIBUTE);
    }

    protected void setIssuer(String issuer, HttpServletRequest request) {
        HttpSession httpSession = request.getSession();
        httpSession.setAttribute(ISSUER_SESSION_ATTRIBUTE, issuer);
    }

    protected String getIssuer(HttpSession httpSession) {
        return (String) httpSession.getAttribute(ISSUER_SESSION_ATTRIBUTE);
    }

    protected void setIdPConfiguration(ServletContext servletContext, IdentityProviderConfiguration configuration) {
        servletContext.setAttribute(IDP_CONFIG_CONTEXT_ATTRIBUTE, configuration);
    }

    public static IdentityProviderConfiguration getIdPConfiguration(ServletContext servletContext) {
        return (IdentityProviderConfiguration) servletContext.getAttribute(IDP_CONFIG_CONTEXT_ATTRIBUTE);
    }

    /**
     * Returns the SAML Response issuer name
     * 
     * @param configuration
     *            IdP configuration
     * @return response issuer
     */
    public static String getResponseIssuer(IdentityProviderConfiguration configuration) {
        String issuerName = configuration.getDefaultIssuer();
        return issuerName;
    }

    private SamlAuthenticationPolicy getAuthenticationPolicy() {

        IdentityProviderFlow authenticationFlow = getAuthenticationFlow();
        switch (authenticationFlow) {

        case IDENTIFICATION:
            return SamlAuthenticationPolicy.IDENTIFICATION;
        case AUTHENTICATION:
            return SamlAuthenticationPolicy.AUTHENTICATION;
        case AUTHENTICATION_WITH_IDENTIFICATION:
            return SamlAuthenticationPolicy.AUTHENTICATION_WITH_IDENTIFICATION;
        }

        throw new RuntimeException("Unsupported authentication flow: " + authenticationFlow);
    }

    protected abstract IdentityProviderFlow getAuthenticationFlow();

    protected abstract ReturnResponse handleSamlResponse(HttpServletRequest request, String targetUrl,
            Response samlResponse, String relayState) throws Exception;
}