Package org.jasig.cas.support.saml.web.view

Source Code of org.jasig.cas.support.saml.web.view.AbstractSaml10ResponseView

/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you 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 the following location:
*
*   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 org.jasig.cas.support.saml.web.view;

import org.jasig.cas.authentication.principal.WebApplicationService;
import org.jasig.cas.support.saml.authentication.principal.SamlService;
import org.jasig.cas.support.saml.util.CasHTTPSOAP11Encoder;
import org.jasig.cas.support.saml.web.support.SamlArgumentExtractor;
import org.jasig.cas.web.view.AbstractCasView;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.common.SAMLVersion;
import org.opensaml.common.binding.BasicSAMLMessageContext;
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
import org.opensaml.saml1.binding.encoding.HTTPSOAP11Encoder;
import org.opensaml.saml1.core.Response;
import org.opensaml.saml1.core.Status;
import org.opensaml.saml1.core.StatusCode;
import org.opensaml.saml1.core.StatusMessage;
import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
import org.opensaml.xml.ConfigurationException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import javax.xml.namespace.QName;
import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

/**
* Base class for all views that render SAML1 SOAP messages directly to the HTTP response stream.
*
* @author Marvin S. Addison
* @since 3.5.1
*/
public abstract class AbstractSaml10ResponseView extends AbstractCasView {

    private static final String DEFAULT_ELEMENT_NAME_FIELD = "DEFAULT_ELEMENT_NAME";

    private static final String DEFAULT_ENCODING = "UTF-8";

    private final SamlArgumentExtractor samlArgumentExtractor = new SamlArgumentExtractor();

    private final HTTPSOAP11Encoder encoder = new CasHTTPSOAP11Encoder();

    private final SecureRandomIdentifierGenerator idGenerator;

    @NotNull
    private String encoding = DEFAULT_ENCODING;

    private int skewAllowance = 0;

    /**
     * Sets the character encoding in the HTTP response.
     *
     * @param encoding Response character encoding.
     */
    public void setEncoding(final String encoding) {
        this.encoding = encoding;
    }

    /**
    * Sets the allowance for time skew in seconds
    * between CAS and the client server.  Default 0s.
    * This value will be subtracted from the current time when setting the SAML
    * <code>NotBeforeDate</code> attribute, thereby allowing for the
    * CAS server to be ahead of the client by as much as the value defined here.
    *
    * <p><strong>Note:</strong> Skewing of the issue instant via setting this property
    * applies to all saml assertions that are issued by CAS and it
    * currently cannot be controlled on a per relying party basis.
    * Before configuring this, it is recommended that each service provider
    * attempt to correctly sync their system time with an NTP server
    * so as to match the CAS server's issue instant config and to
    * avoid applying this setting globally. This should only
    * be used in situations where the NTP server is unresponsive to
    * sync time on the client, or the client is simply unable
    * to adjust their server time configuration.</p>
    *
    * @param skewAllowance Number of seconds to allow for variance.
    */
    public void setSkewAllowance(final int skewAllowance) {
        logger.debug("Using {} seconds as skew allowance.", skewAllowance);
        this.skewAllowance = skewAllowance;
    }

    static {
        try {
            // Initialize OpenSAML default configuration
            // (only needed once per classloader)
            DefaultBootstrap.bootstrap();
        } catch (final ConfigurationException e) {
            throw new IllegalStateException("Error initializing OpenSAML library.", e);
        }
    }

    /**
     * Instantiates a new abstract saml10 response view.
     */
    protected AbstractSaml10ResponseView() {
        try {
            this.idGenerator = new SecureRandomIdentifierGenerator();
        } catch (final NoSuchAlgorithmException e) {
            throw new IllegalStateException("Cannot create secure random ID generator for SAML message IDs.");
        }
    }

    @Override
    protected void renderMergedOutputModel(
            final Map<String, Object> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception {

        response.setCharacterEncoding(this.encoding);

        final WebApplicationService service = this.samlArgumentExtractor.extractService(request);
        final String serviceId = service != null ? service.getId() : "UNKNOWN";

        try {
            final Response samlResponse = newSamlObject(Response.class);
            samlResponse.setID(generateId());
            samlResponse.setIssueInstant(DateTime.now().minusSeconds(skewAllowance));
            samlResponse.setVersion(SAMLVersion.VERSION_11);
            samlResponse.setRecipient(serviceId);
            if (service instanceof SamlService) {
                final SamlService samlService = (SamlService) service;

                if (samlService.getRequestID() != null) {
                    samlResponse.setInResponseTo(samlService.getRequestID());
                }
            }
            prepareResponse(samlResponse, model);

            final BasicSAMLMessageContext messageContext = new BasicSAMLMessageContext();
            messageContext.setOutboundMessageTransport(new HttpServletResponseAdapter(response, request.isSecure()));
            messageContext.setOutboundSAMLMessage(samlResponse);
            this.encoder.encode(messageContext);
        } catch (final Exception e) {
            logger.error("Error generating SAML response for service {}.", serviceId);
            throw e;
        }
    }

    /**
     * Subclasses must implement this method by adding child elements (status, assertion, etc) to
     * the given empty SAML 1 response message.  Impelmenters need not be concerned with error handling.
     *
     * @param response SAML 1 response message to be filled.
     * @param model Spring MVC model map containing data needed to prepare response.
     */
    protected abstract void prepareResponse(Response response, Map<String, Object> model);


    /**
     * Generate id.
     *
     * @return the string
     */
    protected final String generateId() {
        return this.idGenerator.generateIdentifier();
    }

    /**
     * Create a new SAML object.
     *
     * @param <T> the generic type
     * @param objectType the object type
     * @return the t
     */
    protected final <T extends SAMLObject> T newSamlObject(final Class<T> objectType) {
        final QName qName;
        try {
            final Field f = objectType.getField(DEFAULT_ELEMENT_NAME_FIELD);
            qName = (QName) f.get(null);
        } catch (final NoSuchFieldException e) {
            throw new IllegalStateException("Cannot find field " + objectType.getName() + "." + DEFAULT_ELEMENT_NAME_FIELD);
        } catch (final IllegalAccessException e) {
            throw new IllegalStateException("Cannot access field " + objectType.getName() + "." + DEFAULT_ELEMENT_NAME_FIELD);
        }
        final SAMLObjectBuilder<T> builder = (SAMLObjectBuilder<T>) Configuration.getBuilderFactory().getBuilder(qName);
        if (builder == null) {
            throw new IllegalStateException("No SAMLObjectBuilder registered for class " + objectType.getName());
        }
        return objectType.cast(builder.buildObject());
    }

    /**
     * Create a new SAML status object.
     *
     * @param codeValue the code value
     * @param statusMessage the status message
     * @return the status
     */
    protected final Status newStatus(final QName codeValue, final String statusMessage) {
        final Status status = newSamlObject(Status.class);
        final StatusCode code = newSamlObject(StatusCode.class);
        code.setValue(codeValue);
        status.setStatusCode(code);
        if (statusMessage != null) {
            final StatusMessage message = newSamlObject(StatusMessage.class);
            message.setMessage(statusMessage);
            status.setStatusMessage(message);
        }
        return status;
    }
}
TOP

Related Classes of org.jasig.cas.support.saml.web.view.AbstractSaml10ResponseView

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.