package org.apereo.cas.authentication.handler.support; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.authentication.Credential; import org.apereo.cas.authentication.HandlerResult; import org.apereo.cas.authentication.PreventedException; import org.apereo.cas.authentication.UsernamePasswordCredential; import org.apereo.cas.authentication.handler.PrincipalNameTransformer; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.authentication.support.password.PasswordPolicyConfiguration; import org.apereo.cas.services.ServicesManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import javax.security.auth.login.AccountNotFoundException; import javax.security.auth.login.FailedLoginException; import java.security.GeneralSecurityException; import java.util.function.Predicate; /** * Abstract class to override supports so that we don't need to duplicate the * check for UsernamePasswordCredential. * * @author Scott Battaglia * @author Marvin S. Addison * @since 3.0.0 */ public abstract class AbstractUsernamePasswordAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractUsernamePasswordAuthenticationHandler.class); private PasswordEncoder passwordEncoder = NoOpPasswordEncoder.getInstance(); private PrincipalNameTransformer principalNameTransformer = formUserId -> formUserId; private Predicate<Credential> credentialSelectionPredicate = credential -> true; private PasswordPolicyConfiguration passwordPolicyConfiguration; public AbstractUsernamePasswordAuthenticationHandler(final String name, final ServicesManager servicesManager, final PrincipalFactory principalFactory, final Integer order) { super(name, servicesManager, principalFactory, order); } @Override protected HandlerResult doAuthentication(final Credential credential) throws GeneralSecurityException, PreventedException { final UsernamePasswordCredential originalUserPass = (UsernamePasswordCredential) credential; final UsernamePasswordCredential userPass = new UsernamePasswordCredential(originalUserPass.getUsername(), originalUserPass.getPassword()); if (StringUtils.isBlank(userPass.getUsername())) { throw new AccountNotFoundException("Username is null."); } LOGGER.debug("Transforming credential username via [{}]", this.principalNameTransformer.getClass().getName()); final String transformedUsername = this.principalNameTransformer.transform(userPass.getUsername()); if (StringUtils.isBlank(transformedUsername)) { throw new AccountNotFoundException("Transformed username is null."); } if (StringUtils.isBlank(userPass.getPassword())) { throw new FailedLoginException("Password is null."); } LOGGER.debug("Attempting to encode credential password via [{}] for [{}]", this.passwordEncoder.getClass().getName(), transformedUsername); final String transformedPsw = this.passwordEncoder.encode(userPass.getPassword()); if (StringUtils.isBlank(transformedPsw)) { throw new AccountNotFoundException("Encoded password is null."); } userPass.setUsername(transformedUsername); userPass.setPassword(transformedPsw); LOGGER.debug("Attempting authentication internally for transformed credential [{}]", userPass); return authenticateUsernamePasswordInternal(userPass, originalUserPass.getPassword()); } /** * Authenticates a username/password credential by an arbitrary strategy with extra parameter original credential password before * encoding password. Override it if implementation need to use original password for authentication. * * @param transformedCredential the credential object bearing the transformed username and password. * @param originalPassword original password from credential before password encoding * @return HandlerResult resolved from credential on authentication success or null if no principal could be resolved * from the credential. * @throws GeneralSecurityException On authentication failure. * @throws PreventedException On the indeterminate case when authentication is prevented. */ protected abstract HandlerResult authenticateUsernamePasswordInternal(UsernamePasswordCredential transformedCredential, String originalPassword) throws GeneralSecurityException, PreventedException; protected PasswordPolicyConfiguration getPasswordPolicyConfiguration() { return this.passwordPolicyConfiguration; } public void setPasswordEncoder(final PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } public void setCredentialSelectionPredicate(final Predicate<Credential> credentialSelectionPredicate) { this.credentialSelectionPredicate = credentialSelectionPredicate; } public void setPrincipalNameTransformer(final PrincipalNameTransformer principalNameTransformer) { this.principalNameTransformer = principalNameTransformer; } public void setPasswordPolicyConfiguration(final PasswordPolicyConfiguration passwordPolicyConfiguration) { this.passwordPolicyConfiguration = passwordPolicyConfiguration; } @Override public boolean supports(final Credential credential) { if (credential instanceof UsernamePasswordCredential) { if (this.credentialSelectionPredicate != null) { return this.credentialSelectionPredicate.test(credential); } return true; } return false; } /** * Used in case passwordEncoder is used to match raw password with encoded password. Mainly for BCRYPT password encoders where each encoded * password is different and we cannot use traditional compare of encoded strings to check if passwords match * * @param charSequence raw not encoded password * @param password encoded password to compare with * @return true in case charSequence matched encoded password */ protected boolean matches(final CharSequence charSequence, final String password) { return this.passwordEncoder.matches(charSequence, password); } }