package org.apereo.cas.support.saml.util;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.support.saml.OpenSamlConfigBean;
import org.apereo.cas.support.saml.SamlUtils;
import org.apereo.cas.support.saml.authentication.SamlAuthenticationMetaDataPopulator;
import org.apereo.cas.support.saml.authentication.principal.SamlService;
import org.apereo.cas.util.DateTimeUtils;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.saml1.binding.encoding.impl.HTTPSOAP11Encoder;
import org.opensaml.saml.saml1.core.Assertion;
import org.opensaml.saml.saml1.core.Attribute;
import org.opensaml.saml.saml1.core.AttributeStatement;
import org.opensaml.saml.saml1.core.AttributeValue;
import org.opensaml.saml.saml1.core.Audience;
import org.opensaml.saml.saml1.core.AudienceRestrictionCondition;
import org.opensaml.saml.saml1.core.AuthenticationStatement;
import org.opensaml.saml.saml1.core.Conditions;
import org.opensaml.saml.saml1.core.ConfirmationMethod;
import org.opensaml.saml.saml1.core.NameIdentifier;
import org.opensaml.saml.saml1.core.Response;
import org.opensaml.saml.saml1.core.Status;
import org.opensaml.saml.saml1.core.StatusCode;
import org.opensaml.saml.saml1.core.StatusMessage;
import org.opensaml.saml.saml1.core.Subject;
import org.opensaml.saml.saml1.core.SubjectConfirmation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* This is the response builder for Saml1 Protocol.
*
* @author Misagh Moayyed mmoayyed@unicon.net
* @since 4.1
*/
public class Saml10ObjectBuilder extends AbstractSamlObjectBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(Saml10ObjectBuilder.class);
private static final String CONFIRMATION_METHOD = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
private static final long serialVersionUID = -4711012620700270554L;
public Saml10ObjectBuilder(final OpenSamlConfigBean configBean) {
super(configBean);
}
/**
* Create a new SAML response object.
* @param id the id
* @param issueInstant the issue instant
* @param recipient the recipient
* @param service the service
* @return the response
*/
public Response newResponse(final String id, final ZonedDateTime issueInstant,
final String recipient, final WebApplicationService service) {
final Response samlResponse = newSamlObject(Response.class);
samlResponse.setID(id);
samlResponse.setIssueInstant(DateTimeUtils.dateTimeOf(issueInstant));
samlResponse.setVersion(SAMLVersion.VERSION_11);
samlResponse.setInResponseTo(recipient);
setInResponseToForSamlResponseIfNeeded(service, samlResponse);
return samlResponse;
}
/**
* Sets in response to for saml 1 response.
*
* @param service the service
* @param samlResponse the saml 1 response
*/
private static void setInResponseToForSamlResponseIfNeeded(final Service service, final Response samlResponse) {
if (service instanceof SamlService) {
final SamlService samlService = (SamlService) service;
final String requestId = samlService.getRequestID();
if (StringUtils.isNotBlank(requestId)) {
samlResponse.setInResponseTo(requestId);
}
}
}
/**
* Create a new SAML1 response object.
*
* @param authnStatement the authn statement
* @param issuer the issuer
* @param issuedAt the issued at
* @param id the id
* @return the assertion
*/
public Assertion newAssertion(final AuthenticationStatement authnStatement, final String issuer,
final ZonedDateTime issuedAt, final String id) {
final Assertion assertion = newSamlObject(Assertion.class);
assertion.setID(id);
assertion.setIssueInstant(DateTimeUtils.dateTimeOf(issuedAt));
assertion.setIssuer(issuer);
assertion.getAuthenticationStatements().add(authnStatement);
return assertion;
}
/**
* New conditions element.
*
* @param issuedAt the issued at
* @param audienceUri the service id
* @param issueLength the issue length
* @return the conditions
*/
public Conditions newConditions(final ZonedDateTime issuedAt, final String audienceUri, final long issueLength) {
final Conditions conditions = newSamlObject(Conditions.class);
conditions.setNotBefore(DateTimeUtils.dateTimeOf(issuedAt));
conditions.setNotOnOrAfter(DateTimeUtils.dateTimeOf(issuedAt.plus(issueLength, ChronoUnit.MILLIS)));
final AudienceRestrictionCondition audienceRestriction = newSamlObject(AudienceRestrictionCondition.class);
final Audience audience = newSamlObject(Audience.class);
audience.setUri(audienceUri);
audienceRestriction.getAudiences().add(audience);
conditions.getAudienceRestrictionConditions().add(audienceRestriction);
return conditions;
}
/**
* Create a new SAML status object.
*
* @param codeValue the code value
* @param statusMessage the status message
* @return the status
*/
public 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;
}
/**
* New authentication statement.
*
* @param authenticationDate the authentication date
* @param authenticationMethod the authentication method
* @param subjectId the subject id
* @return the authentication statement
*/
public AuthenticationStatement newAuthenticationStatement(final ZonedDateTime authenticationDate,
final Collection<Object> authenticationMethod,
final String subjectId) {
final AuthenticationStatement authnStatement = newSamlObject(AuthenticationStatement.class);
authnStatement.setAuthenticationInstant(DateTimeUtils.dateTimeOf(authenticationDate));
authnStatement.setAuthenticationMethod(
authenticationMethod != null && !authenticationMethod.isEmpty()
? authenticationMethod.iterator().next().toString()
: SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_UNSPECIFIED);
authnStatement.setSubject(newSubject(subjectId));
return authnStatement;
}
/**
* New subject element that uses the confirmation method
* {@link #CONFIRMATION_METHOD}.
*
* @param identifier the identifier
* @return the subject
*/
public Subject newSubject(final String identifier) {
return newSubject(identifier, CONFIRMATION_METHOD);
}
/**
* New subject element with given confirmation method.
*
* @param identifier the identifier
* @param confirmationMethod the confirmation method
* @return the subject
*/
public Subject newSubject(final String identifier, final String confirmationMethod) {
final SubjectConfirmation confirmation = newSamlObject(SubjectConfirmation.class);
final ConfirmationMethod method = newSamlObject(ConfirmationMethod.class);
method.setConfirmationMethod(confirmationMethod);
confirmation.getConfirmationMethods().add(method);
final NameIdentifier nameIdentifier = newSamlObject(NameIdentifier.class);
nameIdentifier.setNameIdentifier(identifier);
final Subject subject = newSamlObject(Subject.class);
subject.setNameIdentifier(nameIdentifier);
subject.setSubjectConfirmation(confirmation);
return subject;
}
/**
* Add saml1 attribute values for attribute.
*
* @param attributeName the attribute name
* @param attributeValue the attribute value
* @param attributeList the attribute list
*/
public void addAttributeValuesToSaml1Attribute(final String attributeName,
final Object attributeValue,
final List<XMLObject> attributeList) {
addAttributeValuesToSamlAttribute(attributeName, attributeValue, attributeList, AttributeValue.DEFAULT_ELEMENT_NAME);
}
/**
* New attribute statement.
*
* @param subject the subject
* @param attributes the attributes
* @param attributeNamespace the attribute namespace
* @return the attribute statement
*/
public AttributeStatement newAttributeStatement(final Subject subject,
final Map<String, Object> attributes,
final String attributeNamespace) {
final AttributeStatement attrStatement = newSamlObject(AttributeStatement.class);
attrStatement.setSubject(subject);
for (final Map.Entry<String, Object> e : attributes.entrySet()) {
if (e.getValue() instanceof Collection<?> && ((Collection<?>) e.getValue()).isEmpty()) {
LOGGER.info("Skipping attribute [{}] because it does not have any values.", e.getKey());
continue;
}
final Attribute attribute = newSamlObject(Attribute.class);
attribute.setAttributeName(e.getKey());
if (StringUtils.isNotBlank(attributeNamespace)) {
attribute.setAttributeNamespace(attributeNamespace);
}
addAttributeValuesToSaml1Attribute(e.getKey(), e.getValue(), attribute.getAttributeValues());
attrStatement.getAttributes().add(attribute);
}
return attrStatement;
}
/**
* Encode response and pass it onto the outbound transport.
* Uses {@link CasHttpSoap11Encoder} to handle encoding.
*
* @param httpResponse the http response
* @param httpRequest the http request
* @param samlMessage the saml response
* @throws Exception the exception in case encoding fails.
*/
public void encodeSamlResponse(final HttpServletResponse httpResponse,
final HttpServletRequest httpRequest,
final Response samlMessage) throws Exception {
SamlUtils.logSamlObject(this.configBean, samlMessage);
final HTTPSOAP11Encoder encoder = new CasHttpSoap11Encoder();
final MessageContext<SAMLObject> context = new MessageContext();
context.setMessage(samlMessage);
encoder.setHttpServletResponse(httpResponse);
encoder.setMessageContext(context);
encoder.initialize();
encoder.prepareContext();
encoder.encode();
}
}