NIHVIVO-3988 Change the handling of multiple root user accounts, and remove the back-door login mechanism.

This commit is contained in:
j2blake 2012-10-08 16:46:31 +00:00
parent 2ecb46adb9
commit f491a7c95c
3 changed files with 64 additions and 207 deletions

View file

@ -2,6 +2,10 @@
package edu.cornell.mannlib.vitro.webapp.auth.policy; package edu.cornell.mannlib.vitro.webapp.auth.policy;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
@ -26,8 +30,10 @@ import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
/** /**
* If the user has an IsRootUser identifier, they can do anything! * If the user has an IsRootUser identifier, they can do anything!
* *
* On setup, check to see that there is a root user. If not, create one. If we * On setup, check to see that the specified root user exists. If not, create
* can't create one, abort. * it. If we can't create it, abort.
*
* If any other root users exist, warn about them.
*/ */
public class RootUserPolicy implements PolicyIface { public class RootUserPolicy implements PolicyIface {
private static final Log log = LogFactory.getLog(RootUserPolicy.class); private static final Log log = LogFactory.getLog(RootUserPolicy.class);
@ -62,8 +68,10 @@ public class RootUserPolicy implements PolicyIface {
public static class Setup implements ServletContextListener { public static class Setup implements ServletContextListener {
private ServletContext ctx; private ServletContext ctx;
private StartupStatus ss; private StartupStatus ss;
private String configRootEmail;
private UserAccountsDao uaDao; private UserAccountsDao uaDao;
private String configuredRootUser;
private boolean configuredRootUserExists;
private TreeSet<String> otherRootUsers;
@Override @Override
public void contextInitialized(ServletContextEvent sce) { public void contextInitialized(ServletContextEvent sce) {
@ -72,14 +80,23 @@ public class RootUserPolicy implements PolicyIface {
try { try {
uaDao = getUserAccountsDao(); uaDao = getUserAccountsDao();
configRootEmail = getRootEmailFromConfig(); configuredRootUser = getRootEmailFromConfig();
checkForWrongRootUser(); otherRootUsers = getEmailsOfAllRootUsers();
configuredRootUserExists = otherRootUsers
.remove(configuredRootUser);
if (rootUserExists()) { if (configuredRootUserExists) {
ss.info(this, "root user is " + configRootEmail); if (otherRootUsers.isEmpty()) {
informThatRootUserExists();
} else {
complainAboutMultipleRootUsers();
}
} else { } else {
createRootUser(); createRootUser();
if (!otherRootUsers.isEmpty()) {
complainAboutWrongRootUsers();
}
} }
ServletPolicyList.addPolicy(ctx, new RootUserPolicy()); ServletPolicyList.addPolicy(ctx, new RootUserPolicy());
@ -110,37 +127,14 @@ public class RootUserPolicy implements PolicyIface {
} }
} }
private void checkForWrongRootUser() { private TreeSet<String> getEmailsOfAllRootUsers() {
UserAccount root = getRootUser(); TreeSet<String> rootUsers = new TreeSet<String>();
if (root == null) {
return;
}
String actualRootEmail = root.getEmailAddress();
if (actualRootEmail.equals(configRootEmail)) {
return;
}
ss.warning(
this,
"The deploy.properties file specifies a root user of '"
+ configRootEmail
+ "', but the system already contains a root user named '"
+ actualRootEmail + "'. The user '"
+ configRootEmail + "' will not be created.");
}
private boolean rootUserExists() {
return (getRootUser() != null);
}
private UserAccount getRootUser() {
for (UserAccount ua : uaDao.getAllUserAccounts()) { for (UserAccount ua : uaDao.getAllUserAccounts()) {
if (ua.isRootUser()) { if (ua.isRootUser()) {
return ua; rootUsers.add(ua.getEmailAddress());
} }
} }
return null; return rootUsers;
} }
/** /**
@ -148,29 +142,21 @@ public class RootUserPolicy implements PolicyIface {
* be forced to edit them. However, that's not in place yet. * be forced to edit them. However, that's not in place yet.
*/ */
private void createRootUser() { private void createRootUser() {
String emailAddress = ConfigurationProperties.getBean(ctx) if (!Authenticator.isValidEmailAddress(configuredRootUser)) {
.getProperty(PROPERTY_ROOT_USER_EMAIL);
if (emailAddress == null) {
throw new IllegalStateException(
"deploy.properties must contain a value for '"
+ PROPERTY_ROOT_USER_EMAIL + "'");
}
if (!Authenticator.isValidEmailAddress(emailAddress)) {
throw new IllegalStateException("Value for '" throw new IllegalStateException("Value for '"
+ PROPERTY_ROOT_USER_EMAIL + PROPERTY_ROOT_USER_EMAIL
+ "' is not a valid email address: '" + emailAddress + "' is not a valid email address: '"
+ "'"); + configuredRootUser + "'");
} }
if (null != uaDao.getUserAccountByEmail(emailAddress)) { if (null != uaDao.getUserAccountByEmail(configuredRootUser)) {
throw new IllegalStateException("Can't create root user - " throw new IllegalStateException("Can't create root user - "
+ "an account already exists with email address '" + "an account already exists with email address '"
+ emailAddress + "'"); + configuredRootUser + "'");
} }
UserAccount ua = new UserAccount(); UserAccount ua = new UserAccount();
ua.setEmailAddress(emailAddress); ua.setEmailAddress(configuredRootUser);
ua.setFirstName("root"); ua.setFirstName("root");
ua.setLastName("user"); ua.setLastName("user");
ua.setMd5Password(Authenticator ua.setMd5Password(Authenticator
@ -182,7 +168,36 @@ public class RootUserPolicy implements PolicyIface {
uaDao.insertUserAccount(ua); uaDao.insertUserAccount(ua);
StartupStatus.getBean(ctx).info(this, StartupStatus.getBean(ctx).info(this,
"Created root user as '" + emailAddress + "'"); "Created root user '" + configuredRootUser + "'");
}
private void informThatRootUserExists() {
ss.info(this, "Root user is " + configuredRootUser);
}
private void complainAboutMultipleRootUsers() {
for (String other : otherRootUsers) {
ss.warning(this, "deploy.properties specifies '"
+ configuredRootUser + "' as the value for '"
+ PROPERTY_ROOT_USER_EMAIL
+ "', but the system also contains this root user: "
+ other);
}
ss.warning(this, "For security, "
+ "it is best to delete unneeded root user accounts.");
}
private void complainAboutWrongRootUsers() {
for (String other : otherRootUsers) {
ss.warning(this, "deploy.properties specifies '"
+ configuredRootUser + "' as the value for '"
+ PROPERTY_ROOT_USER_EMAIL
+ "', but the system contains this root user instead: "
+ other);
}
ss.warning(this, "Creating root user '" + configuredRootUser + "'");
ss.warning(this, "For security, "
+ "it is best to delete unneeded root user accounts.");
} }
@Override @Override

View file

@ -1,149 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.controller.authenticate;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.ibm.icu.text.SimpleDateFormat;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
/**
* Back door to log in as the root user.
*
* If the classpath contains a file called friend.xml, which contains a magic
* line (see below) with today's date, or some date less than a week ago, then
* you are logged in as root.
*
* If anything else, return a 404.
*/
public class FriendController extends HttpServlet {
private static final Log log = LogFactory.getLog(FriendController.class);
private static final long MILLIS_IN_A_WEEK = 7L * 24L * 60L * 60L * 1000L;
// To be valid XML, it could look like this: <date value="2011-07-01" />
// but we don't care as long as it contains this: 9999-99-99
private static final String DATE_PATTERN = "\\d\\d\\d\\d-\\d\\d-\\d\\d";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
if (fileContentsAreAcceptable()) {
writeWarningToTheLog(req);
loginAsRootUser(req);
redirectToHomePage(resp);
}
} catch (Exception e) {
log.debug("problem: " + e.getMessage());
resp.sendError(HttpStatus.SC_NOT_FOUND);
}
}
private boolean fileContentsAreAcceptable() throws Exception {
InputStream stream = null;
try {
stream = openTheFile();
String string = readFromStream(stream);
return checkDateString(string);
} finally {
if (stream != null) {
stream.close();
}
}
}
private InputStream openTheFile() throws Exception {
InputStream stream = this.getClass().getClassLoader()
.getResourceAsStream("/friend.xml");
if (stream == null) {
throw new Exception("can't find the file.");
}
return stream;
}
private String readFromStream(InputStream stream) throws IOException {
byte[] buffer = new byte[1024];
int howMany = stream.read(buffer);
if (howMany == -1) {
return "";
} else {
return new String(buffer, 0, howMany);
}
}
private boolean checkDateString(String string) throws Exception {
long date = parseDateFromFile(string);
compareAgainstDateRange(date);
return true;
}
private long parseDateFromFile(String string) throws Exception {
Pattern p = Pattern.compile(DATE_PATTERN);
Matcher m = p.matcher(string);
if (!m.find()) {
throw new Exception("no date string in the file.");
}
return new SimpleDateFormat("yyyy-MM-dd").parse(m.group()).getTime();
}
private void compareAgainstDateRange(long date) throws Exception {
long now = new Date().getTime();
long then = now - MILLIS_IN_A_WEEK;
if ((date > now) || (date < then)) {
throw new Exception("date out of range.");
}
}
private void writeWarningToTheLog(HttpServletRequest req) {
log.warn("LOGGING IN VIA FRIEND FROM ADDR=" + req.getRemoteAddr()
+ ", PORT=" + req.getRemotePort() + ", HOST="
+ req.getRemoteHost() + ", USER=" + req.getRemoteUser());
}
private void loginAsRootUser(HttpServletRequest req) throws Exception {
UserAccount rootUser = getRootUser(req);
Authenticator.getInstance(req).recordLoginAgainstUserAccount(rootUser,
AuthenticationSource.INTERNAL);
}
private UserAccount getRootUser(HttpServletRequest req) throws Exception {
UserAccountsDao uaDao = new VitroRequest(req).getWebappDaoFactory()
.getUserAccountsDao();
for (UserAccount ua : uaDao.getAllUserAccounts()) {
if (ua.isRootUser()) {
return ua;
}
}
throw new Exception("couldn't find root user.");
}
private void redirectToHomePage(HttpServletResponse resp)
throws IOException {
resp.sendRedirect(UrlBuilder.getUrl("/"));
}
}

View file

@ -1042,15 +1042,6 @@
<url-pattern>/admin/login</url-pattern> <url-pattern>/admin/login</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet>
<servlet-name>friend</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.authenticate.FriendController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>friend</servlet-name>
<url-pattern>/admin/friend</url-pattern>
</servlet-mapping>
<servlet> <servlet>
<servlet-name>logout</servlet-name> <servlet-name>logout</servlet-name>
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Logout</servlet-class> <servlet-class>edu.cornell.mannlib.vitro.webapp.controller.edit.Logout</servlet-class>