package org.apereo.cas.web.flow.resolver.impl; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.AuthenticationContextValidator; import org.apereo.cas.authentication.AuthenticationResultBuilder; import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan; import org.apereo.cas.authentication.AuthenticationSystemSupport; import org.apereo.cas.authentication.Credential; import org.apereo.cas.services.MultifactorAuthenticationProvider; import org.apereo.cas.services.MultifactorAuthenticationProviderSelector; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.ticket.registry.TicketRegistrySupport; import org.apereo.cas.web.flow.CasWebflowConstants; import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver; import org.apereo.cas.web.support.WebUtils; import org.apereo.inspektr.audit.annotation.Audit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.util.CookieGenerator; import org.springframework.webflow.action.EventFactorySupport; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import java.util.Collections; import java.util.Optional; import java.util.Set; /** * This is {@link RankedAuthenticationProviderWebflowEventResolver}. * * @author Misagh Moayyed * @since 5.0.0 */ public class RankedAuthenticationProviderWebflowEventResolver extends AbstractCasWebflowEventResolver { private static final Logger LOGGER = LoggerFactory.getLogger(RankedAuthenticationProviderWebflowEventResolver.class); private final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver; private final AuthenticationContextValidator authenticationContextValidator; public RankedAuthenticationProviderWebflowEventResolver(final AuthenticationSystemSupport authenticationSystemSupport, final CentralAuthenticationService centralAuthenticationService, final ServicesManager servicesManager, final TicketRegistrySupport ticketRegistrySupport, final CookieGenerator warnCookieGenerator, final AuthenticationServiceSelectionPlan authenticationSelectionStrategies, final MultifactorAuthenticationProviderSelector selector, final AuthenticationContextValidator authenticationContextValidator, final CasDelegatingWebflowEventResolver casDelegatingWebflowEventResolver) { super(authenticationSystemSupport, centralAuthenticationService, servicesManager, ticketRegistrySupport, warnCookieGenerator, authenticationSelectionStrategies, selector); this.authenticationContextValidator = authenticationContextValidator; this.initialAuthenticationAttemptWebflowEventResolver = casDelegatingWebflowEventResolver; } @Override public Set<Event> resolveInternal(final RequestContext context) { final String tgt = WebUtils.getTicketGrantingTicketId(context); final RegisteredService service = WebUtils.getRegisteredService(context); if (service == null) { LOGGER.debug("No service is available to determine event for principal"); return resumeFlow(); } if (StringUtils.isBlank(tgt)) { LOGGER.trace("TGT is blank; proceed with flow normally."); return resumeFlow(); } final Authentication authentication = this.ticketRegistrySupport.getAuthenticationFrom(tgt); if (authentication == null) { LOGGER.trace("TGT has no authentication and is blank; proceed with flow normally."); return resumeFlow(); } final Credential credential = WebUtils.getCredential(context); final AuthenticationResultBuilder builder = this.authenticationSystemSupport.establishAuthenticationContextFromInitial(authentication, credential); WebUtils.putAuthenticationResultBuilder(builder, context); WebUtils.putAuthentication(authentication, context); final Event event = this.initialAuthenticationAttemptWebflowEventResolver.resolveSingle(context); if (event == null) { LOGGER.trace("Request does not indicate a requirement for authentication policy; proceed with flow normally."); return resumeFlow(); } final String id = event.getId(); if (id.equals(CasWebflowConstants.TRANSITION_ID_ERROR) || id.equals(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE) || id.equals(CasWebflowConstants.TRANSITION_ID_SUCCESS)) { LOGGER.debug("Returning webflow event as [{}]", id); return Collections.singleton(event); } final Pair<Boolean, Optional<MultifactorAuthenticationProvider>> result = this.authenticationContextValidator.validate(authentication, id, service); if (result.getKey()) { LOGGER.debug("Authentication context is successfully validated by [{}] for service [{}]", id, service); return resumeFlow(); } if (result.getValue().isPresent()) { return Collections.singleton(validateEventIdForMatchingTransitionInContext(id, context, buildEventAttributeMap(authentication.getPrincipal(), service, result.getValue().get()))); } LOGGER.warn("The authentication context cannot be satisfied and the requested event [{}] is unrecognized", id); return Collections.singleton(new Event(this, CasWebflowConstants.TRANSITION_ID_ERROR)); } @Audit(action = "AUTHENTICATION_EVENT", actionResolverName = "AUTHENTICATION_EVENT_ACTION_RESOLVER", resourceResolverName = "AUTHENTICATION_EVENT_RESOURCE_RESOLVER") @Override public Event resolveSingle(final RequestContext context) { return super.resolveSingle(context); } private Set<Event> resumeFlow() { return Collections.singleton(new EventFactorySupport().success(this)); } }