package org.apereo.cas.support.saml.web.view; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.authentication.ProtocolAttributeEncoder; import org.apereo.cas.authentication.principal.WebApplicationService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.services.web.view.AbstractCasView; import org.apereo.cas.support.saml.util.Saml10ObjectBuilder; import org.apereo.cas.web.support.ArgumentExtractor; import org.opensaml.saml.saml1.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.MalformedURLException; import java.net.URL; import java.time.ZoneOffset; import java.time.ZonedDateTime; 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 Logger LOGGER = LoggerFactory.getLogger(AbstractSaml10ResponseView.class); /** * The Saml object builder. */ protected final Saml10ObjectBuilder samlObjectBuilder; /** * Skew time. **/ protected final int skewAllowance; private final ArgumentExtractor samlArgumentExtractor; private final String encoding; /** * Instantiates a new Abstract saml 10 response view. * * @param successResponse the success response * @param protocolAttributeEncoder the protocol attribute encoder * @param servicesManager the services manager * @param authenticationContextAttribute the authentication context attribute * @param samlObjectBuilder the saml object builder * @param samlArgumentExtractor the saml argument extractor * @param encoding Sets the character encoding in the HTTP response. * @param skewAllowance 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} attribute, thereby allowing for the * CAS server to be ahead of the client by as much as the value defined here. * 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. */ public AbstractSaml10ResponseView(final boolean successResponse, final ProtocolAttributeEncoder protocolAttributeEncoder, final ServicesManager servicesManager, final String authenticationContextAttribute, final Saml10ObjectBuilder samlObjectBuilder, final ArgumentExtractor samlArgumentExtractor, final String encoding, final int skewAllowance) { super(successResponse, protocolAttributeEncoder, servicesManager, authenticationContextAttribute); this.samlObjectBuilder = samlObjectBuilder; this.samlArgumentExtractor = samlArgumentExtractor; this.encoding = encoding; LOGGER.debug("Using [{}] seconds as skew allowance.", skewAllowance); this.skewAllowance = skewAllowance; } @Override protected void renderMergedOutputModel(final Map<String, Object> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { String serviceId = null; try { response.setCharacterEncoding(this.encoding); final WebApplicationService service = this.samlArgumentExtractor.extractService(request); if (service == null || StringUtils.isBlank(service.getId())) { serviceId = "UNKNOWN"; } else { try { serviceId = new URL(service.getId()).getHost(); } catch (final MalformedURLException e) { LOGGER.debug(e.getMessage(), e); } } LOGGER.debug("Using [{}] as the recipient of the SAML response for [{}]", serviceId, service); final Response samlResponse = this.samlObjectBuilder.newResponse( this.samlObjectBuilder.generateSecureRandomId(), ZonedDateTime.now(ZoneOffset.UTC).minusSeconds(this.skewAllowance), serviceId, service); LOGGER.debug("Created SAML response for service [{}]", serviceId); prepareResponse(samlResponse, model); LOGGER.debug("Starting to encode SAML response for service [{}]", serviceId); this.samlObjectBuilder.encodeSamlResponse(response, request, samlResponse); } catch (final Exception e) { LOGGER.error("Error generating SAML response for service [{}].", serviceId, e); throw e; } } /** * Subclasses must implement this method by adding child elements (status, assertion, etc) to * the given empty SAML 1 response message. Implementers 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); }