package org.apereo.cas.web.view; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CasProtocolConstants; import org.apereo.cas.authentication.ProtocolAttributeEncoder; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.util.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.View; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Renders and prepares CAS3 views. This view is responsible * to simply just prep the base model, and delegates to * a the real view to render the final output. * * @author Misagh Moayyed * @since 4.1.0 */ public class Cas30ResponseView extends Cas20ResponseView { private static final Logger LOGGER = LoggerFactory.getLogger(Cas30ResponseView.class); private final boolean releaseProtocolAttributes; public Cas30ResponseView(final boolean successResponse, final ProtocolAttributeEncoder protocolAttributeEncoder, final ServicesManager servicesManager, final String authenticationContextAttribute, final View view, final boolean releaseProtocolAttributes) { super(successResponse, protocolAttributeEncoder, servicesManager, authenticationContextAttribute, view); this.releaseProtocolAttributes = releaseProtocolAttributes; } @Override protected void prepareMergedOutputModel(final Map<String, Object> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { super.prepareMergedOutputModel(model, request, response); final Service service = super.getServiceFrom(model); final RegisteredService registeredService = this.servicesManager.findServiceBy(service); final Map<String, Object> attributes = new HashMap<>(); final Map<String, Object> principalAttributes = getCasPrincipalAttributes(model, registeredService); attributes.putAll(principalAttributes); LOGGER.debug("Processed response principal attributes from the output model to be [{}]", principalAttributes.keySet()); if (this.releaseProtocolAttributes) { LOGGER.debug("CAS is configured to release protocol-level attributes. Processing..."); final Map<String, Object> protocolAttributes = getCasProtocolAuthenticationAttributes(model, registeredService); attributes.putAll(protocolAttributes); LOGGER.debug("Processed response protocol/authentication attributes from the output model to be [{}]", protocolAttributes.keySet()); } decideIfCredentialPasswordShouldBeReleasedAsAttribute(attributes, model, registeredService); decideIfProxyGrantingTicketShouldBeReleasedAsAttribute(attributes, model, registeredService); LOGGER.debug("Final collection of attributes for the response are [{}].", attributes.keySet()); putCasResponseAttributesIntoModel(model, attributes, registeredService); } /** * Put cas authentication attributes into model. * * @param model the model * @param registeredService the registered service * @return the cas authentication attributes */ protected Map<String, Object> getCasProtocolAuthenticationAttributes(final Map<String, Object> model, final RegisteredService registeredService) { final Map<String, Object> filteredAuthenticationAttributes = new HashMap<>(getAuthenticationAttributes(model)); filteredAuthenticationAttributes.put(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_AUTHENTICATION_DATE, Collections.singleton(getAuthenticationDate(model))); filteredAuthenticationAttributes.put(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_FROM_NEW_LOGIN, Collections.singleton(isAssertionBackedByNewLogin(model))); filteredAuthenticationAttributes.put(CasProtocolConstants.VALIDATION_REMEMBER_ME_ATTRIBUTE_NAME, Collections.singleton(isRememberMeAuthentication(model))); final String contextProvider = getSatisfiedMultifactorAuthenticationProviderId(model); if (StringUtils.isNotBlank(contextProvider) && StringUtils.isNotBlank(authenticationContextAttribute)) { filteredAuthenticationAttributes.put(this.authenticationContextAttribute, Collections.singleton(contextProvider)); } return filteredAuthenticationAttributes; } /** * Put cas principal attributes into model. * * @param model the model * @param registeredService the registered service * @return the cas principal attributes */ protected Map<String, Object> getCasPrincipalAttributes(final Map<String, Object> model, final RegisteredService registeredService) { return super.getPrincipalAttributesAsMultiValuedAttributes(model); } /** * Put cas response attributes into model. * * @param model the model * @param attributes the attributes * @param registeredService the registered service */ protected void putCasResponseAttributesIntoModel(final Map<String, Object> model, final Map<String, Object> attributes, final RegisteredService registeredService) { LOGGER.debug("Beginning to encode attributes for the response"); final Map<String, Object> encodedAttributes = this.protocolAttributeEncoder.encodeAttributes(attributes, registeredService); LOGGER.debug("Encoded attributes for the response are [{}]", encodedAttributes); super.putIntoModel(model, CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_ATTRIBUTES, encodedAttributes); final List<String> formattedAttributes = new ArrayList<>(encodedAttributes.size()); LOGGER.debug("Beginning to format/render attributes for the response"); encodedAttributes.forEach((k, v) -> { final Set<Object> values = CollectionUtils.toCollection(v); values.forEach(value -> { final String fmt = new StringBuilder() .append("<cas:".concat(k).concat(">")) .append(StringEscapeUtils.escapeXml10(value.toString().trim())) .append("</cas:".concat(k).concat(">")) .toString(); LOGGER.debug("Formatted attribute for the response: [{}]", fmt); formattedAttributes.add(fmt); }); }); super.putIntoModel(model, CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_FORMATTED_ATTRIBUTES, formattedAttributes); } }