package org.apereo.cas.web.flow.resolver.impl;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.AuthenticationException;
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.authentication.principal.Service;
import org.apereo.cas.services.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.RegisteredServiceAccessStrategyUtils;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.AbstractTicketException;
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.flow.resolver.CasWebflowEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.util.CookieGenerator;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This is {@link InitialAuthenticationAttemptWebflowEventResolver},
* which handles the initial authentication attempt and calls upon a number of
* embedded resolvers to produce the next event in the authentication flow.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class InitialAuthenticationAttemptWebflowEventResolver extends AbstractCasWebflowEventResolver implements CasDelegatingWebflowEventResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(InitialAuthenticationAttemptWebflowEventResolver.class);
private final List<CasWebflowEventResolver> orderedResolvers = new ArrayList<>();
private CasWebflowEventResolver selectiveResolver;
public InitialAuthenticationAttemptWebflowEventResolver(final AuthenticationSystemSupport authenticationSystemSupport,
final CentralAuthenticationService centralAuthenticationService,
final ServicesManager servicesManager,
final TicketRegistrySupport ticketRegistrySupport,
final CookieGenerator warnCookieGenerator,
final AuthenticationServiceSelectionPlan authenticationSelectionStrategies,
final MultifactorAuthenticationProviderSelector selector) {
super(authenticationSystemSupport, centralAuthenticationService,
servicesManager, ticketRegistrySupport, warnCookieGenerator,
authenticationSelectionStrategies, selector);
}
@Override
public Set<Event> resolveInternal(final RequestContext context) {
try {
final Credential credential = getCredentialFromContext(context);
final Service service = WebUtils.getService(context);
if (credential != null) {
final AuthenticationResultBuilder builder = this.authenticationSystemSupport.handleInitialAuthenticationTransaction(service, credential);
if (builder.getInitialAuthentication().isPresent()) {
WebUtils.putAuthenticationResultBuilder(builder, context);
WebUtils.putAuthentication(builder.getInitialAuthentication().get(), context);
}
}
if (service != null) {
LOGGER.debug("Locating service [{}] in service registry to determine authentication policy", service);
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(service, registeredService);
LOGGER.debug("Attempting to resolve candidate authentication events for [{}]", service);
final Set<Event> resolvedEvents = resolveCandidateAuthenticationEvents(context, service, registeredService);
if (!resolvedEvents.isEmpty()) {
LOGGER.debug("The set of authentication events resolved for [{}] are [{}]. Beginning to select the final event...",
service, resolvedEvents);
putResolvedEventsAsAttribute(context, resolvedEvents);
final Event finalResolvedEvent = this.selectiveResolver.resolveSingle(context);
LOGGER.debug("The final authentication event resolved for [{}] is [{}]", service, finalResolvedEvent);
if (finalResolvedEvent != null) {
return Collections.singleton(finalResolvedEvent);
}
}
} else {
LOGGER.debug("No target service is specified in the request to determine authentication policy. "
+ "CAS will proceed to the build the authentication event/transaction as usual.");
}
final AuthenticationResultBuilder builder = WebUtils.getAuthenticationResultBuilder(context);
if (builder == null) {
throw new IllegalArgumentException("No authentication result builder can be located in the context");
}
return Collections.singleton(grantTicketGrantingTicketToAuthenticationResult(context, builder, service));
} catch (final Exception e) {
Event event = returnAuthenticationExceptionEventIfNeeded(e);
if (event == null) {
LOGGER.warn(e.getMessage(), e);
event = newEvent(CasWebflowConstants.TRANSITION_ID_ERROR, e);
}
final HttpServletResponse response = WebUtils.getHttpServletResponse(context);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return Collections.singleton(event);
}
}
/**
* Resolve candidate authentication events set.
*
* @param context the context
* @param service the service
* @param registeredService the registered service
* @return the set
*/
protected Set<Event> resolveCandidateAuthenticationEvents(final RequestContext context, final Service service, final RegisteredService registeredService) {
return this.orderedResolvers.stream()
.map(resolver -> resolver.resolveSingle(context))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
@Override
public void addDelegate(final CasWebflowEventResolver r) {
if (r != null) {
orderedResolvers.add(r);
}
}
@Override
public void addDelegate(final CasWebflowEventResolver r, final int index) {
if (r != null) {
orderedResolvers.add(index, r);
}
}
public void setSelectiveResolver(final CasWebflowEventResolver r) {
this.selectiveResolver = r;
}
private Event returnAuthenticationExceptionEventIfNeeded(final Exception e) {
final Exception ex;
if (e instanceof AuthenticationException || e instanceof AbstractTicketException) {
ex = e;
} else if (e.getCause() instanceof AuthenticationException || e.getCause() instanceof AbstractTicketException) {
ex = (Exception) e.getCause();
} else {
return null;
}
LOGGER.debug(ex.getMessage(), ex);
return newEvent(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE, ex);
}
}