NIHVIVO-2694 Add methods and an exception to Authenticator, so we can tell when a user is not allowed to login.

This commit is contained in:
j2blake 2012-02-01 22:03:11 +00:00
parent 0a19ed7d86
commit 6986eb6308
12 changed files with 164 additions and 32 deletions

View file

@ -124,6 +124,11 @@ public class UserAccountsFirstTimeExternalPage extends UserAccountsPage {
+ "'";
return;
}
if (!Authenticator.getInstance(vreq).isUserPermittedToLogin(null)) {
bogusMessage = "User logins are temporarily disabled "
+ "while the system is being maintained.";
return;
}
}
public boolean isBogus() {

View file

@ -13,6 +13,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator.LoginNotPermitted;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginRedirector;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
@ -108,10 +109,15 @@ public class UserAccountsUserController extends FreemarkerHttpServlet {
if (page.isBogus()) {
return showHomePage(vreq, page.getBogusMessage());
} else if (page.isSubmit() && page.isValid()) {
UserAccount userAccount = page.createAccount();
Authenticator auth = Authenticator.getInstance(vreq);
auth.recordLoginAgainstUserAccount(userAccount, EXTERNAL);
return showLoginRedirection(vreq, page.getAfterLoginUrl());
try {
UserAccount userAccount = page.createAccount();
Authenticator auth = Authenticator.getInstance(vreq);
auth.recordLoginAgainstUserAccount(userAccount, EXTERNAL);
return showLoginRedirection(vreq, page.getAfterLoginUrl());
} catch (LoginNotPermitted e) {
// This should have been anticipated by the page.
return showHomePage(vreq, BOGUS_STANDARD_MESSAGE);
}
} else {
return page.showPage();
}

View file

@ -15,6 +15,7 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator.LoginNotPermitted;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
@ -44,6 +45,7 @@ public class AdminLoginController extends FreemarkerHttpServlet {
private static final String MESSAGE_NO_EMAIL_ADDRESS = "errorNoEmail";
private static final String MESSAGE_NO_PASSWORD = "errorNoPassword";
private static final String MESSAGE_LOGIN_DISABLED = "errorLoginDisabled";
private static final String MESSAGE_LOGIN_FAILED = "errorLoginFailed";
private static final String MESSAGE_NEW_PASSWORD_REQUIRED = "newPasswordRequired";
private static final String MESSAGE_NEW_PASSWORD_WRONG_LENGTH = "errorNewPasswordWrongLength";
@ -101,6 +103,9 @@ public class AdminLoginController extends FreemarkerHttpServlet {
if (password.isEmpty()) {
return showForm(MESSAGE_NO_PASSWORD);
}
if (!loginPermitted()) {
return showForm(MESSAGE_LOGIN_DISABLED);
}
if (newPasswordRequired()) {
if (newPassword.isEmpty()) {
return showForm(MESSAGE_NEW_PASSWORD_REQUIRED);
@ -127,6 +132,10 @@ public class AdminLoginController extends FreemarkerHttpServlet {
return showForm(MESSAGE_LOGIN_FAILED);
}
private boolean loginPermitted() {
return auth.isUserPermittedToLogin(userAccount);
}
private boolean newPasswordRequired() {
return auth.isCurrentPassword(userAccount, password)
&& (userAccount.isPasswordChangeRequired());
@ -138,17 +147,21 @@ public class AdminLoginController extends FreemarkerHttpServlet {
}
private boolean tryToLogin() {
if (auth.isCurrentPassword(userAccount, password)) {
auth.recordLoginAgainstUserAccount(userAccount, INTERNAL);
if (!newPassword.isEmpty()) {
auth.recordNewPassword(userAccount, newPassword);
}
return true;
} else {
if (!auth.isCurrentPassword(userAccount, password)) {
return false;
}
try {
auth.recordLoginAgainstUserAccount(userAccount, INTERNAL);
} catch (LoginNotPermitted e) {
return false;
}
if (!newPassword.isEmpty()) {
auth.recordNewPassword(userAccount, newPassword);
}
return true;
}
private ResponseValues showForm(String... codes) {

View file

@ -23,6 +23,14 @@ import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
* This needs to be based on a HttpSession, because things like the UserDAO are
* tied to the session. It seemed easier to base it on a HttpServletRequest,
* which we can use to get the session.
*
* TODO: Wouldn't it be cool if we could remove the LoginNotPermitted exception?
* Perhaps we could have a sub-object called an Authenticator.ForUser, and you
* call a getAuthenticatorForUser() method which returns null if your login has
* been disabled. Then, that object would provide these methods:
* accountRequiresEditing(), getAssociatedIndividualUris(), isCurrentPassword(),
* recordLoginAgainstUserAccount(), recordNewPassword(). If you didn't have such
* an object, you couldn't even call these methods.
*/
public abstract class Authenticator {
// ----------------------------------------------------------------------
@ -48,11 +56,11 @@ public abstract class Authenticator {
public static Authenticator getInstance(HttpServletRequest request) {
ServletContext ctx = request.getSession().getServletContext();
Object attribute = ctx.getAttribute(FACTORY_ATTRIBUTE_NAME);
if (! (attribute instanceof AuthenticatorFactory)) {
if (!(attribute instanceof AuthenticatorFactory)) {
attribute = new BasicAuthenticator.Factory();
ctx.setAttribute(FACTORY_ATTRIBUTE_NAME, attribute);
}
AuthenticatorFactory factory = (AuthenticatorFactory) attribute;
AuthenticatorFactory factory = (AuthenticatorFactory) attribute;
return factory.getInstance(request);
}
@ -77,6 +85,16 @@ public abstract class Authenticator {
*/
public abstract UserAccount getAccountForInternalAuth(String emailAddress);
/**
* Is this user permitted to login? Some Authenticators might disable logins
* for certain users.
*
* Behavior when userAccount is null depends on the particular
* Authenticator. An answer of "true" presumably means that the user will be
* permitted to login and create an account on the fly.
*/
public abstract boolean isUserPermittedToLogin(UserAccount userAccount);
/**
* Internal: does this UserAccount have this password? False if the
* userAccount is null.
@ -113,9 +131,14 @@ public abstract class Authenticator {
* - record the user in the session map
* - notify other users of the model
* </pre>
*
* @throws LoginNotPermitted
* if the Authenticator denies this user the ability to login.
* This should be thrown if and only if isUserPermittedToLogin()
* returns false.
*/
public abstract void recordLoginAgainstUserAccount(UserAccount userAccount,
AuthenticationSource authSource);
AuthenticationSource authSource) throws LoginNotPermitted;
/**
* <pre>
@ -173,4 +196,12 @@ public abstract class Authenticator {
return false;
}
}
// ----------------------------------------------------------------------
// Exceptions
// ----------------------------------------------------------------------
public static class LoginNotPermitted extends Exception {
// no other information
}
}

View file

@ -24,6 +24,11 @@ public class BaseLoginServlet extends HttpServlet {
protected static final Message MESSAGE_LOGIN_FAILED = new LoginProcessBean.Message(
"External login failed.", LoginProcessBean.MLevel.ERROR);
/** Tell the user that it's nothing personal, they just aren't allowed in. */
protected static final Message MESSAGE_LOGIN_DISABLED = new LoginProcessBean.Message(
"User logins are temporarily disabled while the system is being maintained.",
LoginProcessBean.MLevel.ERROR);
protected Authenticator getAuthenticator(HttpServletRequest req) {
return Authenticator.getInstance(req);
}

View file

@ -76,6 +76,13 @@ public class BasicAuthenticator extends Authenticator {
return userAccountsDao.getUserAccountByExternalAuthId(externalAuthId);
}
@Override
public boolean isUserPermittedToLogin(UserAccount userAccount) {
// All users are permitted to login. If the user doesn't have an account
// yet (userAccount is null), an account should be created for them.
return true;
}
@Override
public boolean isCurrentPassword(UserAccount userAccount,
String clearTextPassword) {

View file

@ -17,6 +17,7 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.accounts.user.UserAccountsFirstTimeExternalPage;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator.LoginNotPermitted;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean;
@ -77,6 +78,14 @@ public class LoginExternalAuthReturn extends BaseLoginServlet {
UserAccount userAccount = getAuthenticator(req)
.getAccountForExternalAuth(externalAuthId);
if (!getAuthenticator(req).isUserPermittedToLogin(userAccount)) {
log.debug("Logins disabled for " + userAccount);
complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER,
MESSAGE_LOGIN_DISABLED);
return;
}
if (userAccount == null) {
log.debug("Creating new account for " + externalAuthId
+ ", return to '" + afterLoginUrl + "'");
@ -84,12 +93,20 @@ public class LoginExternalAuthReturn extends BaseLoginServlet {
externalAuthId, afterLoginUrl);
resp.sendRedirect(UrlBuilder.getUrl("/accounts/firstTimeExternal"));
return;
} else {
}
try {
log.debug("Logging in as " + userAccount.getUri());
getAuthenticator(req).recordLoginAgainstUserAccount(userAccount,
AuthenticationSource.EXTERNAL);
new LoginRedirector(req, afterLoginUrl).redirectLoggedInUser(resp);
return;
} catch (LoginNotPermitted e) {
// should have been caught by isUserPermittedToLogin()
log.debug("Logins disabled for " + userAccount);
complainAndReturnToReferrer(req, resp, ATTRIBUTE_REFERRER,
MESSAGE_LOGIN_DISABLED);
return;
}
}

View file

@ -18,6 +18,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator.LoginNotPermitted;
/**
* Provide a means for programmatic login If they provide the right parameters,
@ -29,13 +30,18 @@ public class ProgramLogin extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
new ProgramLoginCore(req, resp).process();
try {
new ProgramLoginCore(req, resp).process();
} catch (LoginNotPermitted e) {
// This should have been prevented by the test for loginDisabled()
throw new ServletException(e);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
new ProgramLoginCore(req, resp).process();
doGet(req, resp);
}
static class ProgramLoginCore {
@ -50,6 +56,8 @@ public class ProgramLogin extends HttpServlet {
+ " parameter is required.";
private static final String MESSAGE_WRONG_USER_OR_PASSWORD = PARAM_EMAIL_ADDRESS
+ " or " + PARAM_PASSWORD + " is incorrect.";
private static final String MESSAGE_LOGIN_DISABLED = "User logins are "
+ "temporarily disabled while the system is being maintained.";
private static final String MESSAGE_NEED_NEW_PASSWORD = "first-time login: "
+ PARAM_NEW_PASSWORD + " parameter is required.";
private static final String MESSAGE_NEW_PASSWORD_NOT_NEEDED = "not first-time login: "
@ -90,7 +98,7 @@ public class ProgramLogin extends HttpServlet {
.getAccountForInternalAuth(this.emailAddress);
}
void process() throws IOException {
void process() throws IOException, LoginNotPermitted {
if (emailAddress.isEmpty()) {
sendError(MESSAGE_NEED_EMAIL_ADDRESS);
return;
@ -104,6 +112,11 @@ public class ProgramLogin extends HttpServlet {
return;
}
if (loginDisabled()) {
sendError(MESSAGE_LOGIN_DISABLED);
return;
}
if (!isPasswordChangeRequired()) {
if (!newPassword.isEmpty()) {
sendError(MESSAGE_NEW_PASSWORD_NOT_NEEDED);
@ -147,6 +160,10 @@ public class ProgramLogin extends HttpServlet {
return auth.isCurrentPassword(userAccount, password);
}
private boolean loginDisabled() {
return !auth.isUserPermittedToLogin(userAccount);
}
private boolean newPasswordIsValidPasswordLength() {
return (newPassword.length() >= MIN_PASSWORD_LENGTH)
&& (newPassword.length() <= MAX_PASSWORD_LENGTH);
@ -160,11 +177,11 @@ public class ProgramLogin extends HttpServlet {
return (userAccount.isPasswordChangeRequired());
}
private void recordLogin() {
private void recordLogin() throws LoginNotPermitted {
auth.recordLoginAgainstUserAccount(userAccount, INTERNAL);
}
private void recordLoginWithPasswordChange() {
private void recordLoginWithPasswordChange() throws LoginNotPermitted {
auth.recordNewPassword(userAccount, newPassword);
auth.recordLoginAgainstUserAccount(userAccount, INTERNAL);
}

View file

@ -33,6 +33,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator.LoginNotPermitted;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginInProcessFlag;
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LoginRedirector;
import edu.cornell.mannlib.vitro.webapp.controller.login.LoginProcessBean;
@ -327,6 +328,11 @@ public class Authenticate extends VitroHttpServlet {
return;
}
if (!getAuthenticator(request).isUserPermittedToLogin(user)) {
bean.setMessage(Message.LOGIN_DISABLED);
return;
}
if (!getAuthenticator(request).isCurrentPassword(user, password)) {
bean.setMessage(Message.INCORRECT_PASSWORD);
return;
@ -336,7 +342,13 @@ public class Authenticate extends VitroHttpServlet {
if (user.isPasswordChangeRequired()) {
transitionToForcedPasswordChange(request);
} else {
transitionToLoggedIn(request, user);
try {
transitionToLoggedIn(request, user);
} catch (LoginNotPermitted e) {
// This should have been caught by isUserPermittedToLogin()
bean.setMessage(Message.LOGIN_DISABLED);
return;
}
}
}
@ -392,7 +404,13 @@ public class Authenticate extends VitroHttpServlet {
}
// New password is acceptable. Store it and go on.
transitionToLoggedIn(request, user, newPassword);
try {
transitionToLoggedIn(request, user, newPassword);
} catch (LoginNotPermitted e) {
// This should have been caught by isUserPermittedToLogin()
bean.setMessage(Message.LOGIN_DISABLED);
return;
}
}
/**
@ -424,7 +442,7 @@ public class Authenticate extends VitroHttpServlet {
* State change: all requirements are satisfied. Log them in.
*/
private void transitionToLoggedIn(HttpServletRequest request,
UserAccount user) {
UserAccount user) throws LoginNotPermitted {
log.debug("Completed login: " + user.getEmailAddress());
getAuthenticator(request).recordLoginAgainstUserAccount(user,
AuthenticationSource.INTERNAL);
@ -435,7 +453,7 @@ public class Authenticate extends VitroHttpServlet {
* log them in.
*/
private void transitionToLoggedIn(HttpServletRequest request,
UserAccount user, String newPassword) {
UserAccount user, String newPassword) throws LoginNotPermitted {
log.debug("Completed login: " + user.getEmailAddress()
+ ", password changed.");
getAuthenticator(request).recordNewPassword(user, newPassword);
@ -477,9 +495,10 @@ public class Authenticate extends VitroHttpServlet {
response.sendRedirect(loginProcessPage);
return;
}
/**
* Exit: user has completed the login. Redirect appropriately and clear the bean.
* Exit: user has completed the login. Redirect appropriately and clear the
* bean.
*/
private void showLoginComplete(HttpServletResponse response,
VitroRequest vreq) throws IOException {
@ -497,12 +516,11 @@ public class Authenticate extends VitroHttpServlet {
}
private LoginRedirector getLoginRedirector(VitroRequest vreq) {
String afterLoginUrl = LoginProcessBean.getBean(vreq).getAfterLoginUrl();
String afterLoginUrl = LoginProcessBean.getBean(vreq)
.getAfterLoginUrl();
return new LoginRedirector(vreq, afterLoginUrl);
}
/** Get a reference to the Authenticator. */
private Authenticator getAuthenticator(HttpServletRequest request) {
return Authenticator.getInstance(request);

View file

@ -125,6 +125,10 @@ public class LoginProcessBean {
public static final Message UNKNOWN_USERNAME = new Message(
"The email or password you entered is incorrect.", MLevel.ERROR);
public static final Message LOGIN_DISABLED = new Message(
"User logins are temporarily disabled while the system is being maintained.",
MLevel.ERROR);
public static final Message INCORRECT_PASSWORD = new Message(
"The email or password you entered is incorrect.", MLevel.ERROR);