package org.apereo.cas.services; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apereo.cas.authentication.Authentication; import org.apereo.cas.authentication.AuthenticationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.webflow.execution.Event; import java.io.Serializable; /** * The {@link AbstractMultifactorAuthenticationProvider} is responsible for * as the parent of all providers. * * @author Misagh Moayyed * @since 4.3 */ public abstract class AbstractMultifactorAuthenticationProvider implements MultifactorAuthenticationProvider, Serializable { private static final long serialVersionUID = 4789727148134156909L; private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMultifactorAuthenticationProvider.class); private MultifactorAuthenticationProviderBypass bypassEvaluator; private String globalFailureMode; private String id; private int order; @Override public String getId() { return id; } @Override public int getOrder() { return this.order; } public void setId(final String id) { this.id = id; } public void setOrder(final int order) { this.order = order; } public void setGlobalFailureMode(final String globalFailureMode) { this.globalFailureMode = globalFailureMode; } @Override public final boolean supports(final Event e, final Authentication authentication, final RegisteredService registeredService) { if (e == null || !e.getId().matches(getId())) { LOGGER.debug("Provided event id [{}] is not applicable to this provider identified by [{}]", e.getId(), getId()); return false; } if (bypassEvaluator != null && !bypassEvaluator.isAuthenticationRequestHonored(authentication, registeredService, this)) { LOGGER.debug("Request cannot be supported by provider [{}] as it's configured for bypass", getId()); return false; } if (supportsInternal(e, authentication, registeredService)) { LOGGER.debug("[{}] voted to support this authentication request", getClass().getSimpleName()); return true; } LOGGER.debug("[{}] voted does not support this authentication request", getClass().getSimpleName()); return false; } /** * Determine internally if provider is able to support this authentication request * for multifactor, and account for bypass rules.. * * @param e the event * @param authentication the authentication * @param registeredService the registered service * @return the boolean */ protected boolean supportsInternal(final Event e, final Authentication authentication, final RegisteredService registeredService) { return true; } @Override public boolean isAvailable(final RegisteredService service) throws AuthenticationException { RegisteredServiceMultifactorPolicy.FailureModes failureMode = RegisteredServiceMultifactorPolicy.FailureModes.CLOSED; final RegisteredServiceMultifactorPolicy policy = service.getMultifactorPolicy(); if (policy != null) { failureMode = policy.getFailureMode(); LOGGER.debug("Multifactor failure mode for [{}] is defined as [{}]", service.getServiceId(), failureMode); } else if (StringUtils.isNotBlank(this.globalFailureMode)) { failureMode = RegisteredServiceMultifactorPolicy.FailureModes.valueOf(this.globalFailureMode); LOGGER.debug("Using global multifactor failure mode for [{}] defined as [{}]", service.getServiceId(), failureMode); } if (failureMode != RegisteredServiceMultifactorPolicy.FailureModes.NONE) { if (isAvailable()) { return true; } if (failureMode == RegisteredServiceMultifactorPolicy.FailureModes.CLOSED) { LOGGER.warn("[{}] could not be reached. Authentication shall fail for [{}]", getClass().getSimpleName(), service.getServiceId()); throw new AuthenticationException(); } LOGGER.warn("[{}] could not be reached. Since the authentication provider is configured for the " + "failure mode of [{}] authentication will proceed without [{}] for service [{}]", getClass().getSimpleName(), failureMode, getClass().getSimpleName(), service.getServiceId()); return false; } LOGGER.debug("Failure mode is set to [{}]. Assuming the provider is available.", failureMode); return true; } /** * Is provider available? * * @return the true/false */ protected boolean isAvailable() { return true; } public void setBypassEvaluator(final MultifactorAuthenticationProviderBypass bypassEvaluator) { this.bypassEvaluator = bypassEvaluator; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj.getClass() != getClass()) { return false; } final MultifactorAuthenticationProvider rhs = (MultifactorAuthenticationProvider) obj; return new EqualsBuilder() .append(this.getOrder(), rhs.getOrder()) .append(this.getId(), rhs.getId()) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder() .append(getOrder()) .append(getId()) .toHashCode(); } @Override public String toString() { return getClass().getSimpleName(); } @Override public boolean matches(final String identifier) { return getId().matches(identifier); } }