package org.apereo.cas.support.saml.web.view; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.ProtocolAttributeEncoder; import org.apereo.cas.authentication.principal.Principal; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.support.saml.util.Saml10ObjectBuilder; import org.apereo.cas.util.CollectionUtils; import org.apereo.cas.util.DateTimeUtils; import org.apereo.cas.CasProtocolConstants; import org.apereo.cas.authentication.RememberMeCredential; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.support.saml.authentication.SamlAuthenticationMetaDataPopulator; import org.apereo.cas.web.support.ArgumentExtractor; import org.opensaml.saml.saml1.core.Assertion; import org.opensaml.saml.saml1.core.AuthenticationStatement; import org.opensaml.saml.saml1.core.Conditions; import org.opensaml.saml.saml1.core.Response; import org.opensaml.saml.saml1.core.StatusCode; import org.opensaml.saml.saml1.core.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.ZonedDateTime; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * Implementation of a view to return a SAML SOAP response and assertion, based on * the SAML 1.1 specification. * <p> * If an AttributePrincipal is supplied, then the assertion will include the * attributes from it (assuming a String key/Object value pair). The only * Authentication attribute it will look at is the authMethod (if supplied). * <p> * Note that this class will currently not handle proxy authentication. * <p> * * @author Scott Battaglia * @author Marvin S. Addison * @since 3.1 */ public class Saml10SuccessResponseView extends AbstractSaml10ResponseView { private static final Logger LOGGER = LoggerFactory.getLogger(Saml10SuccessResponseView.class); private final String issuer; private final String rememberMeAttributeName; private final String defaultAttributeNamespace; public Saml10SuccessResponseView(final ProtocolAttributeEncoder protocolAttributeEncoder, final ServicesManager servicesManager, final String authenticationContextAttribute, final Saml10ObjectBuilder samlObjectBuilder, final ArgumentExtractor samlArgumentExtractor, final String encoding, final int skewAllowance, final String issuer, final String defaultAttributeNamespace) { super(true, protocolAttributeEncoder, servicesManager, authenticationContextAttribute, samlObjectBuilder, samlArgumentExtractor, encoding, skewAllowance); this.issuer = issuer; this.rememberMeAttributeName = CasProtocolConstants.VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME; this.defaultAttributeNamespace = defaultAttributeNamespace; } @Override protected void prepareResponse(final Response response, final Map<String, Object> model) { final ZonedDateTime issuedAt = DateTimeUtils.zonedDateTimeOf(response.getIssueInstant()); final Service service = getAssertionFrom(model).getService(); LOGGER.debug("Preparing SAML response for service [{}]", service); final Authentication authentication = getPrimaryAuthenticationFrom(model); final Collection<Object> authnMethods = CollectionUtils.toCollection(authentication.getAttributes() .get(SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD)); LOGGER.debug("Authentication methods found are [{}]", authnMethods); final Principal principal = getPrincipal(model); final AuthenticationStatement authnStatement = this.samlObjectBuilder.newAuthenticationStatement( authentication.getAuthenticationDate(), authnMethods, principal.getId()); LOGGER.debug("Built authentication statement for [{}] dated at [{}]", principal, authentication.getAuthenticationDate()); final Assertion assertion = this.samlObjectBuilder.newAssertion(authnStatement, this.issuer, issuedAt, this.samlObjectBuilder.generateSecureRandomId()); LOGGER.debug("Built assertion for issuer [{}] dated at [{}]", this.issuer, issuedAt); final Conditions conditions = this.samlObjectBuilder.newConditions(issuedAt, service.getId(), this.skewAllowance); assertion.setConditions(conditions); LOGGER.debug("Built assertion conditions for issuer [{}] and service [{}] ", this.issuer, service.getId()); final Subject subject = this.samlObjectBuilder.newSubject(principal.getId()); LOGGER.debug("Built subject for principal [{}]", principal); final Map<String, Object> attributesToSend = prepareSamlAttributes(model, service); LOGGER.debug("Authentication statement shall include these attributes [{}]", attributesToSend); if (!attributesToSend.isEmpty()) { assertion.getAttributeStatements().add(this.samlObjectBuilder.newAttributeStatement( subject, attributesToSend, this.defaultAttributeNamespace)); } response.setStatus(this.samlObjectBuilder.newStatus(StatusCode.SUCCESS, null)); LOGGER.debug("Set response status code to [{}]", response.getStatus()); response.getAssertions().add(assertion); } /** * Prepare saml attributes. Combines both principal and authentication * attributes. If the authentication is to be remembered, uses {@link #rememberMeAttributeName} * for the remember-me attribute name. * * @param model the model * @return the final map * @since 4.1.0 */ private Map<String, Object> prepareSamlAttributes(final Map<String, Object> model, final Service service) { final Map<String, Object> authnAttributes = new HashMap<>(getAuthenticationAttributesAsMultiValuedAttributes(model)); if (isRememberMeAuthentication(model)) { authnAttributes.remove(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME); authnAttributes.put(this.rememberMeAttributeName, Boolean.TRUE.toString()); } LOGGER.debug("Retrieved authentication attributes [{}] from the model", authnAttributes); final RegisteredService registeredService = this.servicesManager.findServiceBy(service); final Map<String, Object> attributesToReturn = new HashMap<>(); attributesToReturn.putAll(getPrincipalAttributesAsMultiValuedAttributes(model)); attributesToReturn.putAll(authnAttributes); decideIfCredentialPasswordShouldBeReleasedAsAttribute(attributesToReturn, model, registeredService); decideIfProxyGrantingTicketShouldBeReleasedAsAttribute(attributesToReturn, model, registeredService); LOGGER.debug("Beginning to encode attributes [{}] for service [{}]", attributesToReturn, registeredService.getServiceId()); final Map<String, Object> finalAttributes = this.protocolAttributeEncoder.encodeAttributes(attributesToReturn, registeredService); LOGGER.debug("Final collection of attributes are [{}]", finalAttributes); return finalAttributes; } }