fr.mby.saml2.sp.opensaml.query.engine.SloRequestQueryProcessor.java Source code

Java tutorial

Introduction

Here is the source code for fr.mby.saml2.sp.opensaml.query.engine.SloRequestQueryProcessor.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.query.engine;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.joda.time.DateTime;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.SessionIndex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import fr.mby.saml2.sp.api.core.ISaml20IdpConnector;
import fr.mby.saml2.sp.api.core.ISaml20SpProcessor;
import fr.mby.saml2.sp.api.core.SamlBindingEnum;
import fr.mby.saml2.sp.api.exception.NotSignedException;
import fr.mby.saml2.sp.api.exception.SamlBuildingException;
import fr.mby.saml2.sp.api.exception.SamlProcessingException;
import fr.mby.saml2.sp.api.exception.SamlSecurityException;
import fr.mby.saml2.sp.api.exception.SamlValidationException;
import fr.mby.saml2.sp.api.exception.UnsupportedSamlOperation;
import fr.mby.saml2.sp.api.om.IOutgoingSaml;
import fr.mby.saml2.sp.impl.helper.SamlHelper;
import fr.mby.saml2.sp.impl.helper.SamlValidationHelper;
import fr.mby.saml2.sp.impl.query.QuerySloRequest;

/**
 * OpenSaml 2 implementation of QueryProcessor for incoming SLO Response.
 * 
 * @author GIP RECIA 2013 - Maxime BOSSARD.
 * 
 */
public class SloRequestQueryProcessor extends BaseOpenSaml2QueryProcessor<QuerySloRequest, LogoutRequest> {

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

    @Override
    protected void checkSecurity() throws SamlSecurityException {
        final LogoutRequest sloRequest = this.getOpenSamlObject();
        final Issuer issuer = sloRequest.getIssuer();
        final ISaml20IdpConnector idpConnector = this.findIdpConnector(issuer);

        try {
            this.validateSignatureTrust(sloRequest, issuer, idpConnector);
        } catch (final NotSignedException e) {
            throw new SamlSecurityException("The SLO Request cannot be trusted, signature is missing !");
        }
    }

    @Override
    protected void validateConditions() throws SamlValidationException {
        final LogoutRequest sloRequest = this.getOpenSamlObject();

        final int clockSkew = this.getFactory().getClockSkewSeconds();
        final DateTime notOnOrAfter = sloRequest.getNotOnOrAfter();
        SamlValidationHelper.validateTimes(clockSkew, null, notOnOrAfter);
    }

    @Override
    protected void process() throws SamlProcessingException, SamlSecurityException, UnsupportedSamlOperation {
        final LogoutRequest sloRequest = this.getOpenSamlObject();
        final ISaml20SpProcessor spProcessor = this.getSpProcessor();

        // Logout from SP
        final List<SessionIndex> sessionIndexes = sloRequest.getSessionIndexes();
        if (!CollectionUtils.isEmpty(sessionIndexes)) {
            for (final SessionIndex sessionIndex : sessionIndexes) {
                spProcessor.logout(sessionIndex.getSessionIndex());
            }
        }

        // Send SLO Response
        try {
            final SamlBindingEnum binding = SamlBindingEnum.SAML_20_HTTP_POST;
            final ISaml20IdpConnector idpConnector = this.findIdpConnector(sloRequest.getIssuer());
            final IOutgoingSaml sloResponseRequest = this.buildOutgoingSloResponse(sloRequest, binding,
                    idpConnector);
            this.sendSloResponse(binding, sloResponseRequest);
        } catch (final SamlBuildingException e) {
            throw new SamlProcessingException("Unable to build SLO Response to send back to the IdP !", e);
        }
    }

    @Override
    protected QuerySloRequest buildSamlQuery() throws SamlProcessingException, SamlSecurityException {
        final LogoutRequest sloRequest = this.getOpenSamlObject();

        // Incoming Request : No IdP connector builder, build by the IdP !
        final QuerySloRequest query = new QuerySloRequest(sloRequest.getID(), null);

        return query;
    }

    /**
     * Build a SLO Response to send, based on a SLO request.
     * 
     * @param request
     *            the HTTP request
     * @param binding
     *            the SLO Request binding
     * @return the SLO Response to return to the IdP
     * @throws SamlBuildingException
     */
    protected IOutgoingSaml buildOutgoingSloResponse(final LogoutRequest logoutRequest,
            final SamlBindingEnum binding, final ISaml20IdpConnector idpConnector) throws SamlBuildingException {
        Assert.notNull(logoutRequest, "SLO Request must be supplied !");

        final String relayState = SamlHelper.getRelayState(this.getHttpRequest());
        final String originRequestId = logoutRequest.getID();
        final IOutgoingSaml sloResponseRequest = idpConnector.buildSaml20SingleLogoutResponse(binding,
                originRequestId, relayState);

        this.logger.debug("SAML 2.0 Logout Response processing ended.");
        return sloResponseRequest;
    }

    /**
     * Send the SLO Response via the URL Api.
     * 
     * @param binding
     *            the binding to use
     * @param sloResponseRequest
     *            the SLO Response request
     */
    protected void sendSloResponse(final SamlBindingEnum binding, final IOutgoingSaml sloResponseRequest) {
        URL sloUrl = null;
        HttpURLConnection sloConnexion = null;

        try {
            switch (binding) {
            case SAML_20_HTTP_REDIRECT:
                final String redirectUrl = sloResponseRequest.getHttpRedirectBindingUrl();

                sloUrl = new URL(redirectUrl);
                sloConnexion = (HttpURLConnection) sloUrl.openConnection();
                sloConnexion.setReadTimeout(10000);
                sloConnexion.connect();
                break;

            case SAML_20_HTTP_POST:
                final String sloEndpointUrl = sloResponseRequest.getEndpointUrl();
                final Collection<Entry<String, String>> sloPostParams = sloResponseRequest
                        .getHttpPostBindingParams();
                final StringBuffer samlDatas = new StringBuffer(1024);
                final Iterator<Entry<String, String>> itParams = sloPostParams.iterator();
                final Entry<String, String> firstParam = itParams.next();
                samlDatas.append(firstParam.getKey());
                samlDatas.append("=");
                samlDatas.append(firstParam.getValue());
                while (itParams.hasNext()) {
                    final Entry<String, String> param = itParams.next();
                    samlDatas.append("&");
                    samlDatas.append(param.getKey());
                    samlDatas.append("=");
                    samlDatas.append(param.getValue());
                }

                sloUrl = new URL(sloEndpointUrl);
                sloConnexion = (HttpURLConnection) sloUrl.openConnection();
                sloConnexion.setDoInput(true);

                final OutputStreamWriter writer = new OutputStreamWriter(sloConnexion.getOutputStream());
                writer.write(samlDatas.toString());
                writer.flush();
                writer.close();

                sloConnexion.setReadTimeout(10000);
                sloConnexion.connect();
                break;

            default:
                break;
            }

            if (sloConnexion != null) {
                final InputStream responseStream = sloConnexion.getInputStream();

                final StringWriter writer = new StringWriter();
                IOUtils.copy(responseStream, writer, "UTF-8");
                final String response = writer.toString();

                this.logger.debug(String.format("HTTP response to SLO Request sent: [%s] ", response));

                final int responseCode = sloConnexion.getResponseCode();

                final String samlMessage = sloResponseRequest.getSamlMessage();
                final String endpointUrl = sloResponseRequest.getEndpointUrl();
                if (responseCode < 0) {
                    this.logger.error("Unable to send SAML 2.0 Single Logout Response [{}] to endpoint URL [{}] !",
                            samlMessage, endpointUrl);
                } else if (responseCode == 200) {
                    this.logger.info("SAML 2.0 Single Logout Request correctly sent to [{}] !", endpointUrl);
                } else {
                    this.logger.error(
                            "HTTP response code: [{}] ! Error while sending SAML 2.0 Single Logout Request [{}] to endpoint URL [{}] !",
                            new Object[] { responseCode, samlMessage, endpointUrl });
                }
            }

        } catch (final MalformedURLException e) {
            this.logger.error(String.format("Malformed SAML SLO request URL: [%s] !", sloUrl.toExternalForm()), e);
        } catch (final IOException e) {
            this.logger.error(String.format("Unable to send SAML SLO request URL: [%s] !", sloUrl.toExternalForm()),
                    e);
        } finally {
            sloConnexion.disconnect();
        }
    }

}