package org.apereo.cas.web.flow.resolver.impl.mfa; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan; import org.apereo.cas.authentication.AuthenticationSystemSupport; import org.apereo.cas.authentication.principal.Principal; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.configuration.CasConfigurationProperties; 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.authentication.BaseMultifactorAuthenticationProviderEventResolver; 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.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.CookieGenerator; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; /** * This is {@link RestEndpointMultifactorAuthenticationPolicyEventResolver}. * * @author Misagh Moayyed * @since 5.0.0 */ public class RestEndpointMultifactorAuthenticationPolicyEventResolver extends BaseMultifactorAuthenticationProviderEventResolver { private static final Logger LOGGER = LoggerFactory.getLogger(RestEndpointMultifactorAuthenticationPolicyEventResolver.class); private final String restEndpoint; public RestEndpointMultifactorAuthenticationPolicyEventResolver(final AuthenticationSystemSupport authenticationSystemSupport, final CentralAuthenticationService centralAuthenticationService, final ServicesManager servicesManager, final TicketRegistrySupport ticketRegistrySupport, final CookieGenerator warnCookieGenerator, final AuthenticationServiceSelectionPlan authSelectionStrategies, final MultifactorAuthenticationProviderSelector selector, final CasConfigurationProperties casProperties) { super(authenticationSystemSupport, centralAuthenticationService, servicesManager, ticketRegistrySupport, warnCookieGenerator, authSelectionStrategies, selector); this.restEndpoint = casProperties.getAuthn().getMfa().getRestEndpoint(); } @Override public Set<Event> resolveInternal(final RequestContext context) { final RegisteredService service = resolveRegisteredServiceInRequestContext(context); final Authentication authentication = WebUtils.getAuthentication(context); if (service == null || authentication == null) { LOGGER.debug("No service or authentication is available to determine event for principal"); return null; } final Principal principal = authentication.getPrincipal(); if (StringUtils.isBlank(restEndpoint)) { LOGGER.debug("Rest endpoint to determine event is not configured for [{}]", principal.getId()); return null; } final Map<String, MultifactorAuthenticationProvider> providerMap = WebUtils.getAvailableMultifactorAuthenticationProviders(this.applicationContext); if (providerMap == null || providerMap.isEmpty()) { LOGGER.error("No multifactor authentication providers are available in the application context"); return null; } final Collection<MultifactorAuthenticationProvider> flattenedProviders = flattenProviders(providerMap.values()); LOGGER.debug("Contacting [{}] to inquire about [{}]", restEndpoint, principal.getId()); final String results = callRestEndpointForMultifactor(principal, context); if (StringUtils.isNotBlank(results)) { return resolveMultifactorEventViaRestResult(results, flattenedProviders); } LOGGER.debug("No providers are available to match rest endpoint results"); return Collections.emptySet(); } @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); } /** * Resolve multifactor event via rest result collection. * * @param results the results * @param providers the flattened providers * @return the events */ protected Set<Event> resolveMultifactorEventViaRestResult(final String results, final Collection<MultifactorAuthenticationProvider> providers) { LOGGER.debug("Result returned from the rest endpoint is [{}]", results); final MultifactorAuthenticationProvider restProvider = providers.stream() .filter(p -> p.matches(results)) .findFirst() .orElse(null); if (restProvider != null) { LOGGER.debug("Found multifactor authentication provider [{}]", restProvider.getId()); return Collections.singleton(new Event(this, restProvider.getId())); } LOGGER.debug("No multifactor authentication provider could be matched against [{}]", results); return Collections.emptySet(); } /** * Call rest endpoint for multifactor. * * @param principal the principal * @param context the context * @return return the rest response, typically the mfa id. */ protected String callRestEndpointForMultifactor(final Principal principal, final RequestContext context) { final RestTemplate restTemplate = new RestTemplate(); final Service resolvedService = resolveServiceFromAuthenticationRequest(context); final RestEndpointEntity entity = new RestEndpointEntity(principal.getId(), resolvedService.getId()); final ResponseEntity<String> responseEntity = restTemplate.postForEntity(restEndpoint, entity, String.class); if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) { return responseEntity.getBody(); } return null; } /** * The Rest endpoint entity passed along to the API. */ public static class RestEndpointEntity { private String principalId; private String serviceId; public RestEndpointEntity(final String principalId, final String serviceId) { this.principalId = principalId; this.serviceId = serviceId; } public String getPrincipalId() { return principalId; } public void setPrincipalId(final String principalId) { this.principalId = principalId; } public String getServiceId() { return serviceId; } public void setServiceId(final String serviceId) { this.serviceId = serviceId; } } }