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);
}