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());
+ }
+
+}