/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.web.servlet;
import static org.openmrs.web.WebConstants.GP_ALLOWED_LOGIN_ATTEMPTS_PER_IP;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.User;
import org.openmrs.api.context.Context;
import org.openmrs.api.context.ContextAuthenticationException;
import org.openmrs.util.LocaleUtility;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.web.OpenmrsCookieLocaleResolver;
import org.openmrs.web.WebConstants;
import org.openmrs.web.WebUtil;
import org.openmrs.web.user.CurrentUsers;
import org.openmrs.web.user.UserProperties;
/**
* This servlet accepts the username and password from the login form and authenticates the user to
* OpenMRS
*
* @see org.openmrs.api.context.Context#authenticate(String, String)
*/
public class LoginServlet extends HttpServlet {
public static final long serialVersionUID = 134231247523L;
protected static final Log log = LogFactory.getLog(LoginServlet.class);
/**
* The mapping from user's IP address to the number of attempts at logging in from that IP
*/
private Map<String, Integer> loginAttemptsByIP = new HashMap<String, Integer>();
/**
* The mapping from user's IP address to the time that they were locked out
*/
private Map<String, Date> lockoutDateByIP = new HashMap<String, Date>();
/**
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession httpSession = request.getSession();
String ipAddress = request.getRemoteAddr();
Integer loginAttempts = loginAttemptsByIP.get(ipAddress);
if (loginAttempts == null)
loginAttempts = 1;
loginAttempts++;
boolean lockedOut = false;
// look up the allowed # of attempts per IP
Integer allowedLockoutAttempts = 100;
String allowedLockoutAttemptsGP = Context.getAdministrationService().getGlobalProperty(
GP_ALLOWED_LOGIN_ATTEMPTS_PER_IP, "100");
try {
allowedLockoutAttempts = Integer.valueOf(allowedLockoutAttemptsGP.trim());
}
catch (NumberFormatException nfe) {
log.error("Unable to format '" + allowedLockoutAttemptsGP + "' from global property "
+ GP_ALLOWED_LOGIN_ATTEMPTS_PER_IP + " as an integer");
}
// allowing for configurable login attempts here in case network setups are such that all users have the same IP address.
if (allowedLockoutAttempts > 0 && loginAttempts > allowedLockoutAttempts) {
lockedOut = true;
Date lockedOutTime = lockoutDateByIP.get(ipAddress);
if (lockedOutTime != null && new Date().getTime() - lockedOutTime.getTime() > 300000) {
lockedOut = false;
loginAttempts = 0;
lockoutDateByIP.put(ipAddress, null);
} else {
// they haven't been locked out before, or they're trying again
// within the time limit. Set the locked-out date to right now
lockoutDateByIP.put(ipAddress, new Date());
}
}
// get the place to redirect to either now, or after they eventually
// authenticate correctly
String redirect = determineRedirect(request);
if (lockedOut) {
httpSession.setAttribute(WebConstants.OPENMRS_ERROR_ATTR, "auth.login.tooManyAttempts");
} else {
try {
String username = request.getParameter("uname");
String password = request.getParameter("pw");
// only try to authenticate if they actually typed in a username
if (username == null || username.length() == 0)
throw new ContextAuthenticationException("Unable to authenticate with an empty username");
Context.authenticate(username, password);
if (Context.isAuthenticated()) {
httpSession.setAttribute("loginAttempts", 0);
User user = Context.getAuthenticatedUser();
// load the user's default locale if possible
if (user.getUserProperties() != null) {
if (user.getUserProperties().containsKey(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE)) {
String localeString = user.getUserProperty(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE);
Locale locale = WebUtil.normalizeLocale(localeString);
// if locale object is valid we should store it
if (locale != null) {
OpenmrsCookieLocaleResolver oclr = new OpenmrsCookieLocaleResolver();
oclr.setLocale(request, response, locale);
}
}
}
if (new UserProperties(user.getUserProperties()).isSupposedToChangePassword()) {
httpSession.setAttribute(WebConstants.OPENMRS_MSG_ATTR, "User.password.change");
redirect = request.getContextPath() + "/changePassword.form";
}
// In case the user has no preferences, make sure that the context has some locale set
if (Context.getLocale() == null) {
Context.setLocale(LocaleUtility.getDefaultLocale());
}
CurrentUsers.addUser(httpSession, user);
if (log.isDebugEnabled()) {
log.debug("Redirecting after login to: " + redirect);
log.debug("Locale address: " + request.getLocalAddr());
}
response.sendRedirect(redirect);
httpSession.setAttribute(WebConstants.OPENMRS_CLIENT_IP_HTTPSESSION_ATTR, request.getLocalAddr());
httpSession.removeAttribute(WebConstants.OPENMRS_LOGIN_REDIRECT_HTTPSESSION_ATTR);
// unset login attempts by this user because they were
// able to successfully log in
loginAttemptsByIP.remove(ipAddress);
return;
}
}
catch (ContextAuthenticationException e) {
// set the error message for the user telling them
// to try again
httpSession.setAttribute(WebConstants.OPENMRS_ERROR_ATTR, "auth.password.invalid");
}
}
// send the user back the login page because they either
// had a bad password or are locked out
loginAttemptsByIP.put(ipAddress, loginAttempts);
httpSession.setAttribute(WebConstants.OPENMRS_LOGIN_REDIRECT_HTTPSESSION_ATTR, redirect);
response.sendRedirect(request.getContextPath() + "/login.htm");
}
/**
* Convenience method for pulling the correct page to redirect to out of the request
*
* @param request the current request
* @return the page to redirect to as determined by parameters in the request
*/
private String determineRedirect(HttpServletRequest request) {
// first option for redirecting is the "redirect" parameter (set on login.jsp from the session attr)
String redirect = request.getParameter("redirect");
// second option for redirecting is the referrer parameter set at login.jsp
if (redirect == null || redirect.equals("")) {
redirect = request.getParameter("refererURL");
if (redirect != null && !redirect.startsWith("/")) {
// if we have an absolute url, make sure its in our domain
Integer requestURLLength = request.getRequestURL().length();
StringBuffer domainAndPort = request.getRequestURL();
domainAndPort.delete(requestURLLength - request.getRequestURI().length(), requestURLLength);
if (!redirect.startsWith(domainAndPort.toString()))
redirect = null; // send them to the homepage
else {
// now cut out everything but the path
// get the first slash after https:// or http://
redirect = redirect.substring(redirect.indexOf("/", 9));
}
}
}
// third option for redirecting is the main page of the webapp
if (redirect == null || redirect.equals("")) {
redirect = request.getContextPath();
}
// don't redirect back to the login page on success. (I assume the login page is {something}login.{something}
else if (redirect.contains("login.")) {
log.debug("Redirect contains 'login.', redirecting to main page");
redirect = request.getContextPath();
}
// don't redirect to pages outside of openmrs
else if (!redirect.startsWith(request.getContextPath())) {
log.debug("redirect is outside of openmrs, redirecting to main page");
redirect = request.getContextPath();
}
// don't redirect back to the initialsetup page
else if (redirect.endsWith(WebConstants.SETUP_PAGE_URL)) {
log.debug("redirect is back to the setup page because this is their first ever login");
redirect = request.getContextPath();
}
else if (redirect.contains("/options.form") || redirect.contains("/changePassword.form")
|| redirect.contains("/forgotPassword.form")) {
log
.debug("The user was on a page for setting/changing passwords. Send them to the homepage to reduce confusion");
redirect = request.getContextPath();
}
log.debug("Going to use redirect: '" + redirect + "'");
return redirect;
}
}