Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r5369e7509307c773bb6b39d57550d0ea303482b6 -rfd43a187f3eba4f64c02caf4ba1fcd86c3255883 --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 5369e7509307c773bb6b39d57550d0ea303482b6) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision fd43a187f3eba4f64c02caf4ba1fcd86c3255883) @@ -22,6 +22,7 @@ heading.password.changed.screen =Password Changed msg.password.changed =Your password has been changed. error.login =Sorry, that username or password is not known. Please try again. +error.lockedOut =Sorry,you are currently logged out. button.login =Login label.user.guide =[HELP] msg.loading =Loading... Index: lams_central/src/java/org/lamsfoundation/lams/security/UniversalLoginModule.java =================================================================== diff -u -r5773f84ed608838de3521ecde87c52f3c72d478c -rfd43a187f3eba4f64c02caf4ba1fcd86c3255883 --- lams_central/src/java/org/lamsfoundation/lams/security/UniversalLoginModule.java (.../UniversalLoginModule.java) (revision 5773f84ed608838de3521ecde87c52f3c72d478c) +++ lams_central/src/java/org/lamsfoundation/lams/security/UniversalLoginModule.java (.../UniversalLoginModule.java) (revision fd43a187f3eba4f64c02caf4ba1fcd86c3255883) @@ -293,7 +293,7 @@ } // check for internal authentication made by LoginRequestServlet or LoginAsAction - if (inputPassword.startsWith("#")) { + if (inputPassword.startsWith("#LAMS")) { if (UniversalLoginModule.log.isDebugEnabled()) { UniversalLoginModule.log.debug("Authenticating internally user: " + userName); } Index: lams_central/src/java/org/lamsfoundation/lams/web/LoginAsAction.java =================================================================== diff -u -r51fb2a37254f24bb2a805d4ffd54482c779f43fa -rfd43a187f3eba4f64c02caf4ba1fcd86c3255883 --- lams_central/src/java/org/lamsfoundation/lams/web/LoginAsAction.java (.../LoginAsAction.java) (revision 51fb2a37254f24bb2a805d4ffd54482c779f43fa) +++ lams_central/src/java/org/lamsfoundation/lams/web/LoginAsAction.java (.../LoginAsAction.java) (revision fd43a187f3eba4f64c02caf4ba1fcd86c3255883) @@ -75,7 +75,7 @@ // login.jsp knows what to do with these request.setAttribute("login", login); - String token = "#" + RandomPasswordGenerator.nextPassword(10); + String token = "#LAMS" + RandomPasswordGenerator.nextPassword(10); request.setAttribute("password", token); // notify the login module that the user has been authenticated correctly UniversalLoginModule.setAuthenticationToken(token); Index: lams_central/src/java/org/lamsfoundation/lams/web/LoginRequestServlet.java =================================================================== diff -u -r11b64f81e406ff277c7c35988304b0064300de57 -rfd43a187f3eba4f64c02caf4ba1fcd86c3255883 --- lams_central/src/java/org/lamsfoundation/lams/web/LoginRequestServlet.java (.../LoginRequestServlet.java) (revision 11b64f81e406ff277c7c35988304b0064300de57) +++ lams_central/src/java/org/lamsfoundation/lams/web/LoginRequestServlet.java (.../LoginRequestServlet.java) (revision fd43a187f3eba4f64c02caf4ba1fcd86c3255883) @@ -173,7 +173,7 @@ // login.jsp knows what to do with these hses.setAttribute("login", login); - String token = "#" + RandomPasswordGenerator.nextPassword(10); + String token = "#LAMS" + RandomPasswordGenerator.nextPassword(10); hses.setAttribute("password", token); // notify the login module that the user has been authenticated correctly UniversalLoginModule.setAuthenticationToken(token); Index: lams_central/web/login.jsp =================================================================== diff -u -ra175a0599dd4da41780a4a857a0a84151daf18fe -rfd43a187f3eba4f64c02caf4ba1fcd86c3255883 --- lams_central/web/login.jsp (.../login.jsp) (revision a175a0599dd4da41780a4a857a0a84151daf18fe) +++ lams_central/web/login.jsp (.../login.jsp) (revision fd43a187f3eba4f64c02caf4ba1fcd86c3255883) @@ -97,6 +97,13 @@ + +
+
+ +
+
+
Index: lams_common/src/java/org/lamsfoundation/lams/integration/security/SsoHandler.java =================================================================== diff -u -r9eaeb8328024c0c652dd7d17372dc4caf3a5852c -rfd43a187f3eba4f64c02caf4ba1fcd86c3255883 --- lams_common/src/java/org/lamsfoundation/lams/integration/security/SsoHandler.java (.../SsoHandler.java) (revision 9eaeb8328024c0c652dd7d17372dc4caf3a5852c) +++ lams_common/src/java/org/lamsfoundation/lams/integration/security/SsoHandler.java (.../SsoHandler.java) (revision fd43a187f3eba4f64c02caf4ba1fcd86c3255883) @@ -21,6 +21,7 @@ package org.lamsfoundation.lams.integration.security; import java.security.AccessController; +import java.util.Date; import javax.servlet.ServletContext; import javax.servlet.http.Cookie; @@ -30,10 +31,13 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; +import org.apache.log4j.Logger; import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; import org.lamsfoundation.lams.usermanagement.service.UserManagementService; +import org.lamsfoundation.lams.util.Configuration; +import org.lamsfoundation.lams.util.ConfigurationKeys; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; import org.springframework.web.context.WebApplicationContext; @@ -55,6 +59,7 @@ * */ public class SsoHandler implements ServletExtension { + private static Logger log = Logger.getLogger(SsoHandler.class); private static IUserManagementService userManagementService = null; protected static final String SESSION_KEY = "io.undertow.servlet.form.auth.redirect.location"; @@ -72,6 +77,7 @@ // just forward all requests except one for logging in return Handlers.path().addPrefixPath("/", handler).addExactPath("/j_security_check", exchange -> { ServletRequestContext context = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + HttpServletResponse response = (HttpServletResponse) context.getServletResponse(); HttpServletRequest request = (HttpServletRequest) context.getServletRequest(); // initialise jvmRoute for runtime statistics servlet @@ -82,68 +88,81 @@ // recreate session here in case it was invalidated in login.jsp by sysadmin's LoginAs HttpSession session = request.getSession(); - // LoginRequestServlet (integrations) and LoginAsAction (sysadmin) set this parameter - String redirectURL = request.getParameter("redirectURL"); - if (!StringUtils.isBlank(redirectURL)) { - SsoHandler.handleRedirectBack(context, redirectURL); - } - /* * Fetch UserDTO before completing request so putting it later in session is done ASAP * Response is sent in another thread and if UserDTO is not present in session when browser completes * redirect, * it results in error. Winning this race is the easiest option. */ - UserDTO userDTO = null; + String login = request.getParameter("j_username"); - if (!StringUtils.isBlank(login)) { - User user = getUserManagementService(session.getServletContext()).getUserByLogin(login); - if (user != null) { - userDTO = user.getUserDTO(); - - // if user is not yet authorized and has 2FA shared secret set up - redirect him to - // loginTwoFactorAuth.jsp to prompt user to enter his verification code (Time-based One-time Password) - if (request.getRemoteUser() == null && user.isTwoFactorAuthenticationEnabled() - && user.getTwoFactorAuthenticationSecret() != null) { - String verificationCodeStr = request.getParameter("verificationCode"); - int verificationCode = NumberUtils.toInt(verificationCodeStr); - GoogleAuthenticator gAuth = new GoogleAuthenticator(); - boolean isCodeValid = gAuth.authorize(user.getTwoFactorAuthenticationSecret(), - verificationCode); + User user = null; + if (StringUtils.isBlank(login)) { + response.sendRedirect("/lams/login.jsp?failed=true"); + return; + } + user = getUserManagementService(session.getServletContext()).getUserByLogin(login); + if (user == null) { + response.sendRedirect("/lams/login.jsp?failed=true"); + return; + } + String password = request.getParameter("j_password"); + if (user.getLockOutTime() != null && user.getLockOutTime().getTime() > System.currentTimeMillis() + && password != null && !password.startsWith("#LAMS")) { + response.sendRedirect("/lams/login.jsp?lockedOut=true"); + log.debug(user.getFirstName() + " is logged out for " + Configuration.getAsInt(ConfigurationKeys.LOCK_OUT_TIME) + + " mins after " + Configuration.getAsInt(ConfigurationKeys.FAILED_ATTEMPTS) + + " failed attempts."); + return; + } + UserDTO userDTO = user.getUserDTO(); - //user entered correct TOTP password - if (isCodeValid) { - //do nothing and let regular login to happen - - //user hasn't yet entered TOTP password (request came from login.jsp) or entered the wrong one - } else { - session.setAttribute("login", login); - String password = request.getParameter("j_password"); - session.setAttribute("password", password); - - //verificationCodeStr equals null in case request came from login.jsp - String redirectUrl = "/lams/loginTwoFactorAuth.jsp" + ((verificationCodeStr == null) ? "" : "?failed=true" ); - HttpServletResponse response = (HttpServletResponse) context.getServletResponse(); - response.sendRedirect(redirectUrl); - return; - } - - } + // LoginRequestServlet (integrations) and LoginAsAction (sysadmin) set this parameter + String redirectURL = request.getParameter("redirectURL"); + if (!StringUtils.isBlank(redirectURL)) { + SsoHandler.handleRedirectBack(context, redirectURL); + } + + // if user is not yet authorized and has 2FA shared secret set up - redirect him to + // loginTwoFactorAuth.jsp to prompt user to enter his verification code (Time-based One-time Password) + if (request.getRemoteUser() == null && user.isTwoFactorAuthenticationEnabled() + && user.getTwoFactorAuthenticationSecret() != null) { + String verificationCodeStr = request.getParameter("verificationCode"); + int verificationCode = NumberUtils.toInt(verificationCodeStr); + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + boolean isCodeValid = gAuth.authorize(user.getTwoFactorAuthenticationSecret(), verificationCode); + + //user entered correct TOTP password + if (isCodeValid) { + //do nothing and let regular login to happen + + //user hasn't yet entered TOTP password (request came from login.jsp) or entered the wrong one + } else { + session.setAttribute("login", login); + session.setAttribute("password", password); + + //verificationCodeStr equals null in case request came from login.jsp + String redirectUrl = "/lams/loginTwoFactorAuth.jsp" + + ((verificationCodeStr == null) ? "" : "?failed=true"); + response.sendRedirect(redirectUrl); + return; } + } // prevent session fixation attack // This will become obsolete on Undertow upgrade to version 1.1.10+ SessionManager.removeSessionByID(session.getId(), false); request.changeSessionId(); + session = request.getSession(); // store session so UniversalLoginModule can access it SessionManager.startSession(request); // do the logging in UniversalLoginModule or cache handler.handleRequest(exchange); - if (!StringUtils.isBlank(login) && login.equals(request.getRemoteUser())) { + if (login.equals(request.getRemoteUser())) { session.setAttribute(AttributeNames.USER, userDTO); HttpSession existingSession = SessionManager.getSessionForLogin(login); @@ -156,6 +175,32 @@ } // register current session as the only one for the given user SessionManager.addSession(login, session); + Integer failedAttempts = user.getFailedAttempts(); + if (failedAttempts != null && failedAttempts > 0 && password != null + && !password.startsWith("#LAMS")) { + user.setFailedAttempts(null); + user.setLockOutTime(null); + getUserManagementService(session.getServletContext()).save(user); + } + + } else { + Integer failedAttempts = user.getFailedAttempts(); + if (failedAttempts == null) { + failedAttempts = 1; + } else { + failedAttempts++; + } + user.setFailedAttempts(failedAttempts); + Integer failedAttemptsConfig = Configuration.getAsInt(ConfigurationKeys.FAILED_ATTEMPTS); + + if (failedAttempts >= failedAttemptsConfig) { + Integer lockOutTimeConfig = Configuration.getAsInt(ConfigurationKeys.LOCK_OUT_TIME); + Long lockOutTimeMillis = lockOutTimeConfig * 60L * 1000; + Long currentTimeMillis = System.currentTimeMillis(); + Date date = new Date(currentTimeMillis + lockOutTimeMillis); + user.setLockOutTime(date); + } + getUserManagementService(session.getServletContext()).save(user); } SessionManager.endSession();