package net.unicon.cas.addons.response.view.saml;
import org.jasig.cas.authentication.Authentication;
import org.jasig.cas.authentication.SamlAuthenticationMetaDataPopulator;
import org.jasig.cas.authentication.principal.RememberMeCredentials;
import org.jasig.cas.authentication.principal.Service;
import org.joda.time.DateTime;
import org.opensaml.saml1.core.Assertion;
import org.opensaml.saml1.core.Attribute;
import org.opensaml.saml1.core.AttributeStatement;
import org.opensaml.saml1.core.AttributeValue;
import org.opensaml.saml1.core.Audience;
import org.opensaml.saml1.core.AudienceRestrictionCondition;
import org.opensaml.saml1.core.AuthenticationStatement;
import org.opensaml.saml1.core.Conditions;
import org.opensaml.saml1.core.ConfirmationMethod;
import org.opensaml.saml1.core.NameIdentifier;
import org.opensaml.saml1.core.Response;
import org.opensaml.saml1.core.StatusCode;
import org.opensaml.saml1.core.Subject;
import org.opensaml.saml1.core.SubjectConfirmation;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.schema.XSAny;
import org.opensaml.xml.schema.impl.XSAnyBuilder;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.xml.namespace.QName;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
/**
* A replacement implementation for the {@link org.jasig.cas.web.view.Saml10SuccessResponseView}
* that inheits its configuration from {@link NoSamlNamespaceAbstractSaml10ResponseView} so as to override
* the behavior that generates SAML namespaces for a successful SAML assertion.
* @author Misagh Moayyed
* @since 1.7
*/
public final class Saml10SuccessResponseView extends NoSamlNamespaceAbstractSaml10ResponseView {
/** Namespace for custom attributes. */
private static final String NAMESPACE = "http://www.ja-sig.org/products/cas/";
private static final String REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
private static final String REMEMBER_ME_ATTRIBUTE_VALUE = "true";
private static final String CONFIRMATION_METHOD = "urn:oasis:names:tc:SAML:1.0:cm:artifact";
private final XSAnyBuilder attrValueBuilder = (XSAnyBuilder) Configuration.getBuilderFactory().getBuilder(
XSAny.TYPE_NAME);
/** The issuer, generally the hostname. */
@NotNull
private String issuer;
/** The amount of time in milliseconds this is valid for. */
@Min(1000)
private long issueLength = 30000;
@NotNull
private String rememberMeAttributeName = REMEMBER_ME_ATTRIBUTE_NAME;
public Saml10SuccessResponseView() {
super();
}
public Saml10SuccessResponseView(final String issuer, final int issueLength) {
super();
setIssuer(issuer);
setIssueLength(issueLength);
}
@Override
protected void prepareResponse(final Response response, final Map<String, Object> model) {
final Authentication authentication = getAssertionFrom(model).getChainedAuthentications().get(0);
final DateTime issuedAt = response.getIssueInstant();
final Service service = getAssertionFrom(model).getService();
final Object o = authentication.getAttributes().get(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME);
final boolean isRemembered = o == Boolean.TRUE && !getAssertionFrom(model).isFromNewLogin();
// Build up the SAML assertion containing AuthenticationStatement and AttributeStatement
final Assertion assertion = newSamlObject(Assertion.class);
assertion.setID(generateId());
assertion.setIssueInstant(issuedAt);
assertion.setIssuer(this.issuer);
assertion.setConditions(newConditions(issuedAt, service.getId()));
final AuthenticationStatement authnStatement = newAuthenticationStatement(authentication);
assertion.getAuthenticationStatements().add(authnStatement);
final Map<String, Object> attributes = authentication.getPrincipal().getAttributes();
if (!attributes.isEmpty() || isRemembered) {
assertion.getAttributeStatements().add(
newAttributeStatement(newSubject(authentication.getPrincipal().getId()), attributes, isRemembered));
}
response.setStatus(newStatus(StatusCode.SUCCESS, null));
response.getAssertions().add(assertion);
}
private Conditions newConditions(final DateTime issuedAt, final String serviceId) {
final Conditions conditions = newSamlObject(Conditions.class);
conditions.setNotBefore(issuedAt);
conditions.setNotOnOrAfter(issuedAt.plus(this.issueLength));
final AudienceRestrictionCondition audienceRestriction = newSamlObject(AudienceRestrictionCondition.class);
final Audience audience = newSamlObject(Audience.class);
audience.setUri(serviceId);
audienceRestriction.getAudiences().add(audience);
conditions.getAudienceRestrictionConditions().add(audienceRestriction);
return conditions;
}
private Subject newSubject(final String identifier) {
final SubjectConfirmation confirmation = newSamlObject(SubjectConfirmation.class);
final ConfirmationMethod method = newSamlObject(ConfirmationMethod.class);
method.setConfirmationMethod(CONFIRMATION_METHOD);
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;
}
private AuthenticationStatement newAuthenticationStatement(final Authentication authentication) {
final String authenticationMethod = (String) authentication.getAttributes().get(
SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD);
final AuthenticationStatement authnStatement = newSamlObject(AuthenticationStatement.class);
authnStatement.setAuthenticationInstant(new DateTime(authentication.getAuthenticatedDate()));
authnStatement.setAuthenticationMethod(authenticationMethod != null ? authenticationMethod
: SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_UNSPECIFIED);
authnStatement.setSubject(newSubject(authentication.getPrincipal().getId()));
return authnStatement;
}
private AttributeStatement newAttributeStatement(final Subject subject, final Map<String, Object> attributes,
final boolean isRemembered) {
final AttributeStatement attrStatement = newSamlObject(AttributeStatement.class);
attrStatement.setSubject(subject);
for (final Entry<String, Object> e : attributes.entrySet()) {
if (e.getValue() instanceof Collection<?> && ((Collection<?>) e.getValue()).isEmpty()) {
logger.info("Skipping attribute " + e.getKey() + " because it does not have any values.");
continue;
}
final Attribute attribute = newSamlObject(Attribute.class);
attribute.setAttributeName(e.getKey());
attribute.setAttributeNamespace(NAMESPACE);
if (e.getValue() instanceof Collection<?>) {
final Collection<?> c = (Collection<?>) e.getValue();
for (final Object value : c) {
attribute.getAttributeValues().add(newAttributeValue(value, attrStatement));
}
} else {
attribute.getAttributeValues().add(newAttributeValue(e.getValue(), attrStatement));
}
attrStatement.getAttributes().add(attribute);
}
if (isRemembered) {
final Attribute attribute = newSamlObject(Attribute.class);
attribute.setAttributeName(this.rememberMeAttributeName);
attribute.setAttributeNamespace(NAMESPACE);
attribute.getAttributeValues().add(newAttributeValue(REMEMBER_ME_ATTRIBUTE_VALUE, attrStatement));
attrStatement.getAttributes().add(attribute);
}
return attrStatement;
}
private XSAny newAttributeValue(final Object value, final AttributeStatement statement) {
final QName temp = statement.getElementQName();
final QName qName = new QName(temp.getNamespaceURI(), AttributeValue.DEFAULT_ELEMENT_NAME.getLocalPart(),
temp.getPrefix());
final XSAny stringValue = this.attrValueBuilder.buildObject(qName);
if (value instanceof String) {
stringValue.setTextContent((String) value);
} else {
stringValue.setTextContent(value.toString());
}
return stringValue;
}
public void setIssueLength(final long issueLength) {
this.issueLength = issueLength;
}
public void setIssuer(final String issuer) {
this.issuer = issuer;
}
public void setRememberMeAttributeName(final String rememberMeAttributeName) {
this.rememberMeAttributeName = rememberMeAttributeName;
}
}