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.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.web.util.CookieGenerator; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import java.util.Collection; import java.util.Map; import java.util.Set; import static org.springframework.util.StringUtils.commaDelimitedListToSet; /** * This is {@link PrincipalAttributeMultifactorAuthenticationPolicyEventResolver} * that attempts to locate a principal attribute, match its value against * the provided pattern and decide the next event in the flow for the given service. * * @author Misagh Moayyed * @since 5.0.0 */ public class PrincipalAttributeMultifactorAuthenticationPolicyEventResolver extends BaseMultifactorAuthenticationProviderEventResolver { private static final Logger LOGGER = LoggerFactory.getLogger(PrincipalAttributeMultifactorAuthenticationPolicyEventResolver.class); /** Principal attribute value regex. */ protected final String globalPrincipalAttributeValueRegex; /** Principal attribute names. */ protected final Set<String> attributeNames; public PrincipalAttributeMultifactorAuthenticationPolicyEventResolver(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); globalPrincipalAttributeValueRegex = casProperties.getAuthn().getMfa().getGlobalPrincipalAttributeValueRegex(); attributeNames = commaDelimitedListToSet(casProperties.getAuthn().getMfa().getGlobalPrincipalAttributeNameTriggers()); } @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 (attributeNames.isEmpty()) { LOGGER.debug("Attribute name 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; } return resolveMultifactorAuthenticationProvider(context, service, principal, providerMap); } /** * Resolve multifactor authentication provider set. * * @param context the context * @param service the service * @param principal the principal * @param providerMap the provider map * @return the set */ protected Set<Event> resolveMultifactorAuthenticationProvider(final RequestContext context, final RegisteredService service, final Principal principal, final Map<String, MultifactorAuthenticationProvider> providerMap) { final Collection<MultifactorAuthenticationProvider> providers = flattenProviders(providerMap.values()); if (providers.size() == 1 && StringUtils.isNotBlank(globalPrincipalAttributeValueRegex)) { return resolveSingleMultifactorProvider(context, service, principal, providers); } return resolveMultifactorProviderViaPredicate(context, service, principal, providers); } /** * Resolve multifactor provider by regex predicate set. * * @param context the context * @param service the service * @param principal the principal * @param providers the providers * @return the set */ protected Set<Event> resolveMultifactorProviderViaPredicate(final RequestContext context, final RegisteredService service, final Principal principal, final Collection<MultifactorAuthenticationProvider> providers) { return resolveEventViaPrincipalAttribute(principal, attributeNames, service, context, providers, input -> providers.stream() .filter(provider -> input != null && provider.matches(input)) .count() > 0); } /** * Resolve single multifactor provider set. * * @param context the context * @param service the service * @param principal the principal * @param providers the providers * @return the set */ protected Set<Event> resolveSingleMultifactorProvider(final RequestContext context, final RegisteredService service, final Principal principal, final Collection<MultifactorAuthenticationProvider> providers) { final MultifactorAuthenticationProvider provider = providers.iterator().next(); LOGGER.debug("Found a single multifactor provider [{}] in the application context", provider); return resolveEventViaPrincipalAttribute(principal, attributeNames, service, context, providers, input -> input != null && input.matches(globalPrincipalAttributeValueRegex)); } @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); } }