/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.http.servlet; import com.novell.ldapchai.exception.ChaiUnavailableException; import password.pwm.PwmConstants; import password.pwm.bean.UserIdentity; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.HttpMethod; import password.pwm.http.JspUrl; import password.pwm.http.ProcessStatus; import password.pwm.http.PwmRequest; import password.pwm.http.PwmURL; import password.pwm.http.bean.LoginServletBean; import password.pwm.ldap.auth.AuthenticationType; import password.pwm.ldap.auth.PwmAuthenticationSource; import password.pwm.ldap.auth.SessionAuthenticator; import password.pwm.util.CaptchaUtility; import password.pwm.util.PasswordData; import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import password.pwm.ws.server.RestResultBean; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import java.io.IOException; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * User interaction servlet for form-based authentication. Depending on how PWM is deployed, * users may or may not ever visit this servlet. Generally, if PWM is behind iChain, or some * other SSO enabler using HTTP BASIC authentication, this form will not be invoked. * * @author Jason D. Rivard */ @WebServlet( name="LoginServlet", urlPatterns = { PwmConstants.URL_PREFIX_PRIVATE + "/login", PwmConstants.URL_PREFIX_PRIVATE + "/Login" } ) public class LoginServlet extends ControlledPwmServlet { private static final PwmLogger LOGGER = PwmLogger.getLogger(LoginServlet.class.getName()); public enum LoginServletAction implements ProcessAction { login(HttpMethod.POST), restLogin(HttpMethod.POST), receiveUrl(HttpMethod.GET), ; private final HttpMethod method; LoginServletAction(final HttpMethod method) { this.method = method; } public Collection<HttpMethod> permittedMethods() { return Collections.singletonList(method); } } @Override public Class<? extends ProcessAction> getProcessActionsClass() { return LoginServletAction.class; } private boolean passwordOnly(final PwmRequest pwmRequest) { return pwmRequest.isAuthenticated() && pwmRequest.getPwmSession().getLoginInfoBean().getType() == AuthenticationType.AUTH_WITHOUT_PASSWORD; } @Override protected void nextStep(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException { final boolean passwordOnly = passwordOnly(pwmRequest); forwardToJSP(pwmRequest, passwordOnly); } @Override public ProcessStatus preProcessCheck(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException, ServletException { return ProcessStatus.Continue; } @ActionHandler(action = "login") private ProcessStatus processLogin(final PwmRequest pwmRequest) throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException { final boolean passwordOnly = passwordOnly(pwmRequest); final Map<String,String> valueMap = pwmRequest.readParametersAsMap(); try { handleLoginRequest(pwmRequest, valueMap, passwordOnly); } catch (PwmOperationalException e) { setLastError(pwmRequest, e.getErrorInformation()); forwardToJSP(pwmRequest, passwordOnly); return ProcessStatus.Halt; } // login has succeeded pwmRequest.sendRedirect(determinePostLoginUrl(pwmRequest)); return ProcessStatus.Halt; } @ActionHandler(action = "restLogin") private ProcessStatus processRestLogin(final PwmRequest pwmRequest) throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException { final boolean passwordOnly = passwordOnly(pwmRequest); final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap(); if (valueMap == null || valueMap.isEmpty()) { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"missing json request body"); pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest)); return ProcessStatus.Halt; } try { handleLoginRequest(pwmRequest, valueMap, passwordOnly); } catch (PwmOperationalException e) { final ErrorInformation errorInformation = e.getErrorInformation(); LOGGER.trace(pwmRequest, "returning rest login error to client: " + errorInformation.toDebugStr()); pwmRequest.outputJsonResult(RestResultBean.fromError(errorInformation, pwmRequest)); return ProcessStatus.Halt; } pwmRequest.readParametersAsMap(); // login has succeeded final String nextLoginUrl = determinePostLoginUrl(pwmRequest); final RestResultBean restResultBean = new RestResultBean(); final HashMap<String,String> resultMap = new HashMap<>(Collections.singletonMap("nextURL", nextLoginUrl)); restResultBean.setData(resultMap); LOGGER.debug(pwmRequest, "rest login succeeded"); pwmRequest.outputJsonResult(restResultBean); return ProcessStatus.Halt; } @ActionHandler(action = "receiveUrl") private ProcessStatus processReceiveUrl(final PwmRequest pwmRequest) throws PwmUnrecoverableException, IOException { final String encryptedNextUrl = pwmRequest.readParameterAsString(PwmConstants.PARAM_POST_LOGIN_URL); if (!StringUtil.isEmpty(encryptedNextUrl)) { final String nextUrl = pwmRequest.getPwmApplication().getSecureService().decryptStringValue(encryptedNextUrl); if (!StringUtil.isEmpty(nextUrl)) { final LoginServletBean loginServletBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, LoginServletBean.class); LOGGER.trace(pwmRequest, "received nextUrl and storing in module bean, value: " + nextUrl); loginServletBean.setNextUrl(nextUrl); } } pwmRequest.sendRedirect(PwmServletDefinition.Login); return ProcessStatus.Halt; } private void handleLoginRequest( final PwmRequest pwmRequest, final Map<String,String> valueMap, final boolean passwordOnly ) throws PwmOperationalException, ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException { final String username = valueMap.get(PwmConstants.PARAM_USERNAME); final String passwordStr = valueMap.get(PwmConstants.PARAM_PASSWORD); final PasswordData password = passwordStr != null && passwordStr.length() > 0 ? new PasswordData(passwordStr) : null; final String context = valueMap.get(PwmConstants.PARAM_CONTEXT); final String ldapProfile = valueMap.get(PwmConstants.PARAM_LDAP_PROFILE); final String recaptchaResponse = valueMap.get("g-recaptcha-response"); if (!passwordOnly && (username == null || username.isEmpty())) { throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"missing username parameter")); } if (password == null) { throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,"missing password parameter")); } if (!CaptchaUtility.verifyReCaptcha(pwmRequest, recaptchaResponse)) { throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_BAD_CAPTCHA_RESPONSE, "captcha incorrect")); } final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator( pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), PwmAuthenticationSource.LOGIN_FORM ); if (passwordOnly) { final UserIdentity userIdentity = pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity(); sessionAuthenticator.authenticateUser(userIdentity, password); } else { sessionAuthenticator.searchAndAuthenticateUser(username, password, context, ldapProfile); } // if here then login was successful // recycle the session to prevent session fixation attack. pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded(true); } private void forwardToJSP( final PwmRequest pwmRequest, final boolean passwordOnly ) throws IOException, ServletException, PwmUnrecoverableException { final JspUrl url = passwordOnly ? JspUrl.LOGIN_PW_ONLY : JspUrl.LOGIN; pwmRequest.forwardToJsp(url); } private static String determinePostLoginUrl(final PwmRequest pwmRequest) throws PwmUnrecoverableException { final LoginServletBean loginServletBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, LoginServletBean.class); final String decryptedValue = loginServletBean.getNextUrl(); if (decryptedValue != null && !decryptedValue.isEmpty()) { final PwmURL originalPwmURL = new PwmURL(URI.create(decryptedValue),pwmRequest.getContextPath()); if (!originalPwmURL.isLoginServlet()) { loginServletBean.setNextUrl(null); return decryptedValue; } } return pwmRequest.getContextPath(); } public static void redirectToLoginServlet(final PwmRequest pwmRequest) throws IOException, PwmUnrecoverableException { //store the original requested url final String originalRequestedUrl = pwmRequest.getURLwithQueryString(); final String encryptedRedirUrl = pwmRequest.getPwmApplication().getSecureService().encryptToString(originalRequestedUrl); final Map<String,String> paramMap = new HashMap<>(); paramMap.put(PwmConstants.PARAM_POST_LOGIN_URL, encryptedRedirUrl); paramMap.put(PwmConstants.PARAM_ACTION_REQUEST, LoginServletAction.receiveUrl.toString()); final String redirectUrl = PwmURL.appendAndEncodeUrlParameters( pwmRequest.getContextPath() + PwmServletDefinition.Login.servletUrl(), paramMap ); LOGGER.trace(pwmRequest, "redirecting to self to set nextUrl to: " + originalRequestedUrl); pwmRequest.sendRedirect(redirectUrl); } }