package org.pac4j.http.client.indirect; import org.pac4j.core.client.IndirectClient; import org.pac4j.core.redirect.RedirectAction; import org.pac4j.core.context.HttpConstants; import org.pac4j.core.context.Pac4jConstants; import org.pac4j.core.context.WebContext; import org.pac4j.core.credentials.authenticator.Authenticator; import org.pac4j.core.exception.CredentialsException; import org.pac4j.core.exception.HttpAction; import org.pac4j.core.profile.CommonProfile; import org.pac4j.core.profile.creator.ProfileCreator; import org.pac4j.core.util.CommonHelper; import org.pac4j.core.credentials.extractor.FormExtractor; import org.pac4j.core.credentials.UsernamePasswordCredentials; /** * <p>This class is the client to authenticate users through HTTP form.</p> * <p>The login url of the form must be defined through the {@link #setLoginUrl(String)} method. For authentication, the user is redirected to * this login form. The username and password inputs must be posted on the callback url. Their names can be defined by using the * {@link #setUsernameParameter(String)} and {@link #setPasswordParameter(String)} methods.</p> * * @author Jerome Leleu * @since 1.4.0 */ public class FormClient extends IndirectClient<UsernamePasswordCredentials, CommonProfile> { private String loginUrl; public final static String ERROR_PARAMETER = "error"; public final static String MISSING_FIELD_ERROR = "missing_field"; private String usernameParameter = Pac4jConstants.USERNAME; private String passwordParameter = Pac4jConstants.PASSWORD; public FormClient() { } public FormClient(final String loginUrl, final Authenticator usernamePasswordAuthenticator) { this.loginUrl = loginUrl; defaultAuthenticator(usernamePasswordAuthenticator); } public FormClient(final String loginUrl, final String usernameParameter, final String passwordParameter, final Authenticator usernamePasswordAuthenticator) { this.loginUrl = loginUrl; this.usernameParameter = usernameParameter; this.passwordParameter = passwordParameter; defaultAuthenticator(usernamePasswordAuthenticator); } public FormClient(final String loginUrl, final Authenticator usernamePasswordAuthenticator, final ProfileCreator profileCreator) { this.loginUrl = loginUrl; defaultAuthenticator(usernamePasswordAuthenticator); defaultProfileCreator(profileCreator); } @Override protected void clientInit(final WebContext context) { CommonHelper.assertNotBlank("loginUrl", this.loginUrl); CommonHelper.assertNotBlank("usernameParameter", this.usernameParameter); CommonHelper.assertNotBlank("passwordParameter", this.passwordParameter); defaultRedirectActionBuilder(ctx -> { final String finalLoginUrl = urlResolver.compute(this.loginUrl, ctx); return RedirectAction.redirect(finalLoginUrl); }); defaultCredentialsExtractor(new FormExtractor(usernameParameter, passwordParameter, getName())); } @Override protected UsernamePasswordCredentials retrieveCredentials(final WebContext context) throws HttpAction { CommonHelper.assertNotNull("credentialsExtractor", getCredentialsExtractor()); CommonHelper.assertNotNull("authenticator", getAuthenticator()); final String username = context.getRequestParameter(this.usernameParameter); UsernamePasswordCredentials credentials; try { // retrieve credentials credentials = getCredentialsExtractor().extract(context); logger.debug("usernamePasswordCredentials: {}", credentials); if (credentials == null) { throw handleInvalidCredentials(context, username, "Username and password cannot be blank -> return to the form with error", MISSING_FIELD_ERROR); } // validate credentials getAuthenticator().validate(credentials, context); } catch (final CredentialsException e) { throw handleInvalidCredentials(context, username, "Credentials validation fails -> return to the form with error", computeErrorMessage(e)); } return credentials; } protected HttpAction handleInvalidCredentials(final WebContext context, final String username, String message, String errorMessage) throws HttpAction { // it's an AJAX request -> unauthorized (instead of a redirection) if (getAjaxRequestResolver().isAjax(context)) { final String msg = "AJAX request detected -> returning 401"; logger.info(msg); return HttpAction.status(msg, HttpConstants.UNAUTHORIZED, context); } else { String redirectionUrl = CommonHelper.addParameter(this.loginUrl, this.usernameParameter, username); redirectionUrl = CommonHelper.addParameter(redirectionUrl, ERROR_PARAMETER, errorMessage); logger.debug("redirectionUrl: {}", redirectionUrl); logger.debug(message); return HttpAction.redirect(message, context, redirectionUrl); } } /** * Return the error message depending on the thrown exception. Can be overriden for other message computation. * * @param e the technical exception * @return the error message */ protected String computeErrorMessage(final Exception e) { return e.getClass().getSimpleName(); } public String getLoginUrl() { return this.loginUrl; } public void setLoginUrl(final String loginUrl) { this.loginUrl = loginUrl; } public String getUsernameParameter() { return this.usernameParameter; } public void setUsernameParameter(final String usernameParameter) { this.usernameParameter = usernameParameter; } public String getPasswordParameter() { return this.passwordParameter; } public void setPasswordParameter(final String passwordParameter) { this.passwordParameter = passwordParameter; } @Override public String toString() { return CommonHelper.toString(this.getClass(), "callbackUrl", this.callbackUrl, "name", getName(), "loginUrl", this.loginUrl, "usernameParameter", this.usernameParameter, "passwordParameter", this.passwordParameter, "redirectActionBuilder", getRedirectActionBuilder(), "extractor", getCredentialsExtractor(), "authenticator", getAuthenticator(), "profileCreator", getProfileCreator()); } }