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.AuthenticationException; import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan; import org.apereo.cas.authentication.AuthenticationSystemSupport; 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.util.ResourceUtils; import org.apereo.cas.util.ScriptingUtils; import org.apereo.cas.web.flow.authentication.BaseMultifactorAuthenticationProviderEventResolver; import org.apereo.cas.web.support.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.springframework.web.util.CookieGenerator; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.Set; /** * This is {@link GroovyScriptMultifactorAuthenticationPolicyEventResolver} * that conditionally evaluates a groovy script to resolve the mfa provider id * and event. * * @author Misagh Moayyed * @since 5.1.0 */ public class GroovyScriptMultifactorAuthenticationPolicyEventResolver extends BaseMultifactorAuthenticationProviderEventResolver { private static final Logger LOGGER = LoggerFactory.getLogger(GlobalMultifactorAuthenticationPolicyEventResolver.class); private final Resource groovyScript; public GroovyScriptMultifactorAuthenticationPolicyEventResolver(final AuthenticationSystemSupport authenticationSystemSupport, final CentralAuthenticationService centralAuthenticationService, final ServicesManager servicesManager, final TicketRegistrySupport ticketRegistrySupport, final CookieGenerator warnCookieGenerator, final AuthenticationServiceSelectionPlan authenticationSelectionStrategies, final MultifactorAuthenticationProviderSelector selector, final CasConfigurationProperties casProperties) { super(authenticationSystemSupport, centralAuthenticationService, servicesManager, ticketRegistrySupport, warnCookieGenerator, authenticationSelectionStrategies, selector); groovyScript = casProperties.getAuthn().getMfa().getGroovyScript(); } @Override public Set<Event> resolveInternal(final RequestContext context) { final Service service = resolveServiceFromAuthenticationRequest(context); final RegisteredService registeredService = resolveRegisteredServiceInRequestContext(context); final Authentication authentication = WebUtils.getAuthentication(context); if (groovyScript == null || !ResourceUtils.doesResourceExist(groovyScript)) { LOGGER.debug("No groovy script is found or configured for multifactor authentication"); return null; } if (authentication == null) { LOGGER.debug("No authentication is available to determine event for principal"); return null; } if (registeredService == null || service == null) { LOGGER.debug("No registeredService is available to determine event for principal [{}]", authentication.getPrincipal()); 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"); throw new AuthenticationException(); } try { final Object[] args = {service, registeredService, authentication, LOGGER}; final String provider = ScriptingUtils.executeGroovyScript(groovyScript, args, String.class); if (StringUtils.isBlank(provider)) { return null; } final Optional<MultifactorAuthenticationProvider> providerFound = resolveProvider(providerMap, provider); if (providerFound.isPresent()) { if (providerFound.get().isAvailable(registeredService)) { final Event event = validateEventIdForMatchingTransitionInContext(providerFound.get().getId(), context, buildEventAttributeMap(authentication.getPrincipal(), registeredService, providerFound.get())); return Collections.singleton(event); } LOGGER.warn("Located multifactor provider [{}], yet the provider cannot be reached or verified", providerFound.get()); return null; } LOGGER.warn("No multifactor provider could be found for [{}]", provider); throw new AuthenticationException(); } catch (final Exception e) { LOGGER.error(e.getMessage(), e); } return null; } }