diff --git a/webapp/config/web.xml b/webapp/config/web.xml index 39e4b8fd9..aced09075 100644 --- a/webapp/config/web.xml +++ b/webapp/config/web.xml @@ -1097,6 +1097,15 @@ edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginExternalAuthReturn + + programLogin + edu.cornell.mannlib.vitro.webapp.controller.authenticate.ProgramLogin + + + programLogin + /programLogin + + logout edu.cornell.mannlib.vitro.webapp.controller.edit.Logout diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLogin.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLogin.java new file mode 100644 index 000000000..410f44543 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLogin.java @@ -0,0 +1,116 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.authenticate; + +import static edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource.INTERNAL; +import static edu.cornell.mannlib.vitro.webapp.beans.User.MAX_PASSWORD_LENGTH; +import static edu.cornell.mannlib.vitro.webapp.beans.User.MIN_PASSWORD_LENGTH; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import edu.cornell.mannlib.vitro.webapp.beans.User; + +/** + * Provide a means for programmatic login If they provide the right parameters, + * log them in and send 200. Otherwise, send 403 error. + */ +public class ProgramLogin extends HttpServlet { + public static final String PARAM_USERNAME = "username"; + public static final String PARAM_PASSWORD = "password"; + public static final String PARAM_NEW_PASSWORD = "newPassword"; + public static final int ERROR_CODE = 403; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + Authenticator auth = Authenticator.getInstance(req); + + String username = req.getParameter(PARAM_USERNAME); + String password = req.getParameter(PARAM_PASSWORD); + String newPassword = req.getParameter(PARAM_NEW_PASSWORD); + + // username is required + if ((username == null) || username.isEmpty()) { + resp.sendError(ERROR_CODE, PARAM_USERNAME + + " parameter is required."); + return; + } + + // password is required + if ((password == null) || password.isEmpty()) { + resp.sendError(ERROR_CODE, PARAM_PASSWORD + + " parameter is required."); + return; + } + + // user must exist and password must be correct + if (!auth.isExistingUser(username) + || (!auth.isCurrentPassword(username, password))) { + resp.sendError(ERROR_CODE, PARAM_USERNAME + " or " + PARAM_PASSWORD + + " is incorrect."); + return; + } + + User user = auth.getUserByUsername(username); + boolean firstTime = (user.getLoginCount() == 0); + + if (firstTime) { + // on first-time login, new password is required + if ((newPassword == null) || newPassword.isEmpty()) { + resp.sendError(ERROR_CODE, "first-time login: " + + PARAM_NEW_PASSWORD + " parameter is required."); + return; + } + + // on first-time login, new password must be correct length + if ((newPassword.length() < MIN_PASSWORD_LENGTH) + || (newPassword.length() > MAX_PASSWORD_LENGTH)) { + resp.sendError(ERROR_CODE, PARAM_PASSWORD + " must be between " + + MIN_PASSWORD_LENGTH + " and " + MAX_PASSWORD_LENGTH + + " characters."); + return; + } + + // on first-time login, new password must be different from old + if (auth.isCurrentPassword(username, newPassword)) { + resp.sendError(ERROR_CODE, PARAM_NEW_PASSWORD + + " must not be the same as " + PARAM_PASSWORD); + return; + } + + auth.recordNewPassword(username, newPassword); + auth.recordLoginAgainstUserAccount(username, INTERNAL); + sendSuccess(resp, "first-time login successful."); + return; + } else { + // not first-time login, new password is not allowed + if ((newPassword != null) && (!newPassword.isEmpty())) { + resp.sendError(ERROR_CODE, "not first-time login: " + + PARAM_NEW_PASSWORD + " parameter is not allowed."); + return; + } + + auth.recordLoginAgainstUserAccount(username, INTERNAL); + sendSuccess(resp, "login successful."); + return; + } + } + + private void sendSuccess(HttpServletResponse resp, String message) + throws IOException { + PrintWriter writer = resp.getWriter(); + writer.println(message); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + doGet(req, resp); + } +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java new file mode 100644 index 000000000..d8961d577 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ProgramLoginTest.java @@ -0,0 +1,211 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.authenticate; + +import static edu.cornell.mannlib.vitro.webapp.controller.authenticate.ProgramLogin.PARAM_NEW_PASSWORD; +import static edu.cornell.mannlib.vitro.webapp.controller.authenticate.ProgramLogin.PARAM_PASSWORD; +import static edu.cornell.mannlib.vitro.webapp.controller.authenticate.ProgramLogin.PARAM_USERNAME; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.URL; +import java.util.Date; + +import javax.servlet.ServletException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Level; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import stubs.javax.servlet.ServletConfigStub; +import stubs.javax.servlet.ServletContextStub; +import stubs.javax.servlet.http.HttpServletRequestStub; +import stubs.javax.servlet.http.HttpServletResponseStub; +import stubs.javax.servlet.http.HttpSessionStub; +import edu.cornell.mannlib.vedit.beans.LoginStatusBean; +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.beans.User; +import edu.cornell.mannlib.vitro.webapp.controller.edit.Authenticate; + +/** + * Test the basic features of ProgramTest. + */ +public class ProgramLoginTest extends AbstractTestClass { + private static final Log log = LogFactory.getLog(ProgramLoginTest.class); + + private static final String NEW_USER_URI = "new_user_uri"; + private static final String NEW_USER_NAME = "new_user"; + private static final String NEW_USER_PASSWORD = "new_user_pw"; + private static final User NEW_USER = createUser(NEW_USER_URI, + NEW_USER_NAME, NEW_USER_PASSWORD, 0); + + private static final String OLD_USER_URI = "old_user_uri"; + private static final String OLD_USER_NAME = "old_user"; + private static final String OLD_USER_PASSWORD = "old_user_pw"; + private static final User OLD_USER = createUser(OLD_USER_URI, + OLD_USER_NAME, OLD_USER_PASSWORD, 10); + + private AuthenticatorStub authenticator; + private ServletContextStub servletContext; + private ServletConfigStub servletConfig; + private HttpSessionStub session; + private HttpServletRequestStub request; + private HttpServletResponseStub response; + private ProgramLogin servlet; + + @Before + public void setLogging() { + setLoggerLevel(this.getClass(), Level.DEBUG); + } + + @Before + public void setup() throws Exception { + authenticator = AuthenticatorStub.setup(); + authenticator.addUser(NEW_USER); + authenticator.addUser(OLD_USER); + + servletContext = new ServletContextStub(); + + servletConfig = new ServletConfigStub(); + servletConfig.setServletContext(servletContext); + + servlet = new ProgramLogin(); + servlet.init(servletConfig); + + session = new HttpSessionStub(); + session.setServletContext(servletContext); + + request = new HttpServletRequestStub(); + request.setSession(session); + request.setRequestUrl(new URL("http://this.that/vivo/programLogin")); + request.setMethod("GET"); + + response = new HttpServletResponseStub(); + } + + private static User createUser(String uri, String name, String password, + int loginCount) { + User user = new User(); + user.setUsername(name); + user.setURI(uri); + user.setRoleURI(String.valueOf(50)); + user.setMd5password(Authenticate.applyMd5Encoding(password)); + user.setLoginCount(loginCount); + if (loginCount > 0) { + user.setFirstTime(new Date(0)); + } + return user; + } + + @After + public void cleanup() { + if (servlet != null) { + servlet.destroy(); + } + } + + @Test + public void noUsername() { + executeRequest(null, null, null); + assert403(); + } + + @Test + public void noPassword() { + executeRequest(OLD_USER_NAME, null, null); + assert403(); + } + + @Test + public void unrecognizedUser() { + executeRequest("bogusUsername", "bogusPassword", null); + assert403(); + } + + @Test + public void wrongPassword() { + executeRequest(OLD_USER_NAME, "bogusPassword", null); + assert403(); + } + + @Test + public void success() { + executeRequest(OLD_USER_NAME, OLD_USER_PASSWORD, null); + assertSuccess(); + } + + @Test + public void newPasswordNotNeeded() { + executeRequest(OLD_USER_NAME, OLD_USER_PASSWORD, "unneededPW"); + assert403(); + } + + @Test + public void newPasswordMissing() { + executeRequest(NEW_USER_NAME, NEW_USER_PASSWORD, null); + assert403(); + } + + @Test + public void newPasswordTooLong() { + executeRequest(NEW_USER_NAME, NEW_USER_PASSWORD, "reallyLongPassword"); + assert403(); + } + + @Test + public void newPasswordEqualsOldPassword() { + executeRequest(NEW_USER_NAME, NEW_USER_PASSWORD, NEW_USER_PASSWORD); + assert403(); + } + + @Test + public void successWithNewPassword() { + executeRequest(NEW_USER_NAME, NEW_USER_PASSWORD, "newerBetter"); + assertSuccess(); + } + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + private void executeRequest(String username, String password, + String newPassword) { + if (username != null) { + request.addParameter(PARAM_USERNAME, username); + } + if (password != null) { + request.addParameter(PARAM_PASSWORD, password); + } + if (newPassword != null) { + request.addParameter(PARAM_NEW_PASSWORD, newPassword); + } + + try { + servlet.doGet(request, response); + } catch (ServletException e) { + log.error(e, e); + fail(e.toString()); + } catch (IOException e) { + log.error(e, e); + fail(e.toString()); + } + } + + private void assert403() { + assertEquals("status", 403, response.getStatus()); + log.debug("Message was '" + response.getErrorMessage() + "'"); + assertEquals("logged in", false, LoginStatusBean.getBean(session) + .isLoggedIn()); + } + + private void assertSuccess() { + assertEquals("status", 200, response.getStatus()); + assertEquals("logged in", true, LoginStatusBean.getBean(session) + .isLoggedIn()); + } + +}