package org.apereo.cas.services.web.view; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CasProtocolConstants; import org.apereo.cas.CasViewConstants; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.ProtocolAttributeEncoder; import org.apereo.cas.authentication.RememberMeCredential; import org.apereo.cas.authentication.principal.Principal; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.validation.Assertion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.view.AbstractView; import java.time.ZonedDateTime; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Abstract class to handle retrieving the Assertion from the model. * * @author Scott Battaglia * @since 3.1 */ public abstract class AbstractCasView extends AbstractView { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCasView.class); /** * Indicate whether this view will be generating the success response or not. * By default, the view is treated as a failure. */ protected final boolean successResponse; /** * The attribute encoder instance. */ protected final ProtocolAttributeEncoder protocolAttributeEncoder; /** * The Services manager. */ protected final ServicesManager servicesManager; /** * authentication context attribute name. */ protected final String authenticationContextAttribute; public AbstractCasView(final boolean successResponse, final ProtocolAttributeEncoder protocolAttributeEncoder, final ServicesManager servicesManager, final String authenticationContextAttribute) { this.successResponse = successResponse; this.protocolAttributeEncoder = protocolAttributeEncoder; this.servicesManager = servicesManager; this.authenticationContextAttribute = authenticationContextAttribute; } /** * Gets the assertion from the model. * * @param model the model * @return the assertion from */ protected Assertion getAssertionFrom(final Map<String, Object> model) { return (Assertion) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_ASSERTION); } /** * Gets error code from. * * @param model the model * @return the error code from */ protected String getErrorCodeFrom(final Map<String, Object> model) { return model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_ERROR_CODE).toString(); } /** * Gets error description from. * * @param model the model * @return the error description from */ protected String getErrorDescriptionFrom(final Map<String, Object> model) { return model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_ERROR_DESCRIPTION).toString(); } /** * Gets the PGT from the model. * * @param model the model * @return the pgt id */ protected String getProxyGrantingTicketId(final Map<String, Object> model) { return (String) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET); } /** * Gets the PGT-IOU from the model. * * @param model the model * @return the pgt-iou id */ protected String getProxyGrantingTicketIou(final Map<String, Object> model) { return (String) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET_IOU); } /** * Gets the authentication from the model. * * @param model the model * @return the assertion from * @since 4.1.0 */ protected Authentication getPrimaryAuthenticationFrom(final Map<String, Object> model) { return getAssertionFrom(model).getPrimaryAuthentication(); } /** * Gets model attributes. * * @param model the model * @return the model attributes */ protected Map<String, Object> getModelAttributes(final Map<String, Object> model) { return (Map<String, Object>) model.get(CasProtocolConstants.VALIDATION_CAS_MODEL_ATTRIBUTE_NAME_ATTRIBUTES); } /** * Gets authentication attributes from the primary authentication object. * * @param model the model * @return the authentication attribute */ protected Map<String, Object> getAuthenticationAttributes(final Map<String, Object> model) { final Authentication authn = getPrimaryAuthenticationFrom(model); return authn.getAttributes(); } /** * Gets an authentication attribute from the primary authentication object. * * @param model the model * @param attributeName the attribute name * @return the authentication attribute */ protected String getAuthenticationAttribute(final Map<String, Object> model, final String attributeName) { final Authentication authn = getPrimaryAuthenticationFrom(model); return (String) authn.getAttributes().get(attributeName); } /** * Gets the principal from the model. * * @param model the model * @return the assertion from * @since 4.1.0 */ protected Principal getPrincipal(final Map<String, Object> model) { return getPrimaryAuthenticationFrom(model).getPrincipal(); } /** * Gets principal attributes. * Single-valued attributes are converted to a collection * so the review can easily loop through all. * * @param model the model * @return the attributes * @see #convertAttributeValuesToMultiValuedObjects(java.util.Map) * @since 4.1.0 */ protected Map<String, Object> getPrincipalAttributesAsMultiValuedAttributes(final Map<String, Object> model) { return convertAttributeValuesToMultiValuedObjects(getPrincipal(model).getAttributes()); } /** * Gets authentication attributes. * Single-valued attributes are converted to a collection * so the review can easily loop through all. * * @param model the model * @return the attributes * @see #convertAttributeValuesToMultiValuedObjects(java.util.Map) * @since 4.1.0 */ protected Map<String, Object> getAuthenticationAttributesAsMultiValuedAttributes(final Map<String, Object> model) { return convertAttributeValuesToMultiValuedObjects(getPrimaryAuthenticationFrom(model).getAttributes()); } /** * Is remember me authentication? * looks at the authentication object to find {@link RememberMeCredential#AUTHENTICATION_ATTRIBUTE_REMEMBER_ME} * and expects the assertion to also note a new login session. * * @param model the model * @return true if remember-me, false if otherwise. */ protected boolean isRememberMeAuthentication(final Map<String, Object> model) { final Map<String, Object> authnAttributes = getAuthenticationAttributesAsMultiValuedAttributes(model); final Collection authnMethod = (Collection) authnAttributes.get(RememberMeCredential.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME); return authnMethod != null && authnMethod.contains(Boolean.TRUE) && isAssertionBackedByNewLogin(model); } /** * Gets satisfied multifactor authentication provider. * * @param model the model * @return the satisfied multifactor authentication provider */ protected String getSatisfiedMultifactorAuthenticationProviderId(final Map<String, Object> model) { if (StringUtils.isNotBlank(authenticationContextAttribute) && model.containsKey(this.authenticationContextAttribute)) { return model.get(this.authenticationContextAttribute).toString(); } return null; } /** * Is assertion backed by new login? * * @param model the model * @return true/false. */ protected boolean isAssertionBackedByNewLogin(final Map<String, Object> model) { return getAssertionFrom(model).isFromNewLogin(); } /** * Convert attribute values to multi valued objects. * * @param attributes the attributes * @return the map of attributes to return */ private static Map<String, Object> convertAttributeValuesToMultiValuedObjects(final Map<String, Object> attributes) { final Set<Map.Entry<String, Object>> entries = attributes.entrySet(); return entries.stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> { final Object value = entry.getValue(); if (value instanceof Collection || value instanceof Map || value instanceof Object[] || value instanceof Iterator || value instanceof Enumeration) { return value; } return Collections.singleton(value); })); } /** * Gets authentication date. * * @param model the model * @return the authentication date * @since 4.1.0 */ protected ZonedDateTime getAuthenticationDate(final Map<String, Object> model) { return getPrimaryAuthenticationFrom(model).getAuthenticationDate(); } /** * Gets validated service from the model. * * @param model the model * @return the validated service from */ protected Service getServiceFrom(final Map<String, Object> model) { return (Service) model.get(CasViewConstants.MODEL_ATTRIBUTE_NAME_SERVICE); } /** * Gets chained authentications. * Note that the last index in the list always describes the primary authentication * event. All others in the chain should denote proxies. Per the CAS protocol, * when authentication has proceeded through multiple proxies, * the order in which the proxies were traversed MUST be reflected in the response. * The most recently-visited proxy MUST be the first proxy listed, and all the * other proxies MUST be shifted down as new proxies are added. * * @param model the model * @return the chained authentications */ protected Collection<Authentication> getChainedAuthentications(final Map<String, Object> model) { final Assertion assertion = getAssertionFrom(model); final List<Authentication> chainedAuthentications = assertion.getChainedAuthentications(); return chainedAuthentications.stream().limit(chainedAuthentications.size() - 1).collect(Collectors.toList()); } /** * Decide if credential password should be released as attribute. * The credential must have been cached as an authentication attribute * and the attribute release policy must be allowed to release the * attribute. * * @param attributes the attributes * @param model the model * @param service the service */ protected void decideIfCredentialPasswordShouldBeReleasedAsAttribute(final Map<String, Object> attributes, final Map<String, Object> model, final RegisteredService service) { final RegisteredServiceAttributeReleasePolicy policy = service.getAttributeReleasePolicy(); final boolean isAuthorized = policy != null && policy.isAuthorizedToReleaseCredentialPassword(); decideAttributeReleaseBasedOnServiceAttributePolicy(attributes, getAuthenticationAttribute(model, CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL), CasViewConstants.MODEL_ATTRIBUTE_NAME_PRINCIPAL_CREDENTIAL, service, isAuthorized); } /** * Decide if PGT should be released as attribute. * The PGT must have been cached as an authentication attribute * and the attribute release policy must be allowed to release the * attribute. * * @param attributes the attributes * @param model the model * @param service the service */ protected void decideIfProxyGrantingTicketShouldBeReleasedAsAttribute(final Map<String, Object> attributes, final Map<String, Object> model, final RegisteredService service) { final RegisteredServiceAttributeReleasePolicy policy = service.getAttributeReleasePolicy(); final boolean isAuthorized = policy != null && policy.isAuthorizedToReleaseProxyGrantingTicket(); decideAttributeReleaseBasedOnServiceAttributePolicy(attributes, getProxyGrantingTicketId(model), CasViewConstants.MODEL_ATTRIBUTE_NAME_PROXY_GRANTING_TICKET, service, isAuthorized); } /** * Decide attribute release based on service attribute policy. * * @param attributes the attributes * @param attributeValue the attribute value * @param attributeName the attribute name * @param service the service * @param doesAttributePolicyAllow does attribute policy allow release of this attribute? */ protected void decideAttributeReleaseBasedOnServiceAttributePolicy(final Map<String, Object> attributes, final String attributeValue, final String attributeName, final RegisteredService service, final boolean doesAttributePolicyAllow) { if (StringUtils.isNotBlank(attributeValue)) { LOGGER.debug("Obtained [{}] as an authentication attribute", attributeName); if (doesAttributePolicyAllow) { LOGGER.debug("Obtained [{}] is passed to the CAS validation payload", attributeName); attributes.put(attributeName, Collections.singleton(attributeValue)); } else { LOGGER.debug("Attribute release policy for [{}] does not authorize the release of [{}]", service.getServiceId(), attributeName); attributes.remove(attributeName); } } else { LOGGER.trace("[{}] is not available and will not be released to the validation response.", attributeName); } } /** * Put into model. * * @param model the model * @param key the key * @param value the value */ protected void putIntoModel(final Map<String, Object> model, final String key, final Object value) { model.put(key, value); } /** * Put all into model. * * @param model the model * @param values the values */ protected void putAllIntoModel(final Map<String, Object> model, final Map<String, Object> values) { model.putAll(values); } public ProtocolAttributeEncoder getProtocolAttributeEncoder() { return this.protocolAttributeEncoder; } public ServicesManager getServicesManager() { return this.servicesManager; } public String getAuthenticationContextAttribute() { return authenticationContextAttribute; } }