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:
parent
0a19ed7d86
commit
6986eb6308
12 changed files with 164 additions and 32 deletions
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue