package com.occamlab.te.web.listeners; import java.io.File; import java.io.FileOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.xerces.impl.Constants; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.bootstrap.DOMImplementationRegistry; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; import com.occamlab.te.SetupOptions; import com.occamlab.te.realm.PasswordStorage; /** * A context listener that checks for the presence of clear text passwords in * user info files (<code>{TE_BASE}/users/{userid}/user.xml</code>). If one is * found that does not conform to the expected hash format, it is replaced with * the corresponding hash. * <p> * The password digest must be generated using the {@link PasswordStorage * PBKDF2} function; it consists of five fields separated by the colon (':') * character. For example: * <code>sha1:64000:18:a6BHX18eMTR1WnCvyR6NzG6VMJcdJE2D:8qPU0jpdPIapbyC+H5dqiaNE</code> * </p> */ @WebListener public class CleartextPasswordContextListener implements ServletContextListener { private static final Logger LOGR = Logger.getLogger(CleartextPasswordContextListener.class.getPackage().getName()); @Override public void contextDestroyed(ServletContextEvent evt) { } /** * Checks that passwords in the user files are not in clear text. If a hash * value is found for some user, it is assumed that all user passwords have * previously been hashed and no further checks are done. */ @Override public void contextInitialized(ServletContextEvent evt) { File usersDir = new File(SetupOptions.getBaseConfigDirectory(), "users"); if (!usersDir.isDirectory()) { return; } DocumentBuilder domBuilder = null; try { domBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { LOGR.warning(e.getMessage()); return; } DOMImplementationLS lsFactory = buildDOM3LoadAndSaveFactory(); LSSerializer serializer = lsFactory.createLSSerializer(); serializer.getDomConfig().setParameter(Constants.DOM_XMLDECL, Boolean.FALSE); serializer.getDomConfig().setParameter(Constants.DOM_FORMAT_PRETTY_PRINT, Boolean.TRUE); LSOutput output = lsFactory.createLSOutput(); output.setEncoding("UTF-8"); for (File userDir : usersDir.listFiles()) { File userFile = new File(userDir, "user.xml"); if (!userFile.isFile()) { continue; } try { Document doc = domBuilder.parse(userFile); Node pwNode = doc.getElementsByTagName("password").item(0); if (null == pwNode) { continue; } String password = pwNode.getTextContent(); if (password.split(":").length == 5) { break; } pwNode.setTextContent(PasswordStorage.createHash(password)); // overwrite contents of file output.setByteStream(new FileOutputStream(userFile, false)); serializer.write(doc, output); } catch (Exception e) { LOGR.info(e.getMessage()); continue; } } } /** * Builds a DOMImplementationLS factory that supports the * "DOM Level 3 Load and Save" specification. It provides various factory * methods for creating the objects required for loading and saving DOM * nodes. * * @return A factory object, or <code>null</code> if one cannot be created * (in which case a warning will be logged). * * @see <a href="https://www.w3.org/TR/DOM-Level-3-LS/">Document Object * Model (DOM) Level 3 Load and Save Specification, Version 1.0</a> */ DOMImplementationLS buildDOM3LoadAndSaveFactory() { DOMImplementationLS factory = null; try { DOMImplementationRegistry domRegistry = DOMImplementationRegistry.newInstance(); factory = (DOMImplementationLS) domRegistry.getDOMImplementation("LS 3.0"); } catch (Exception e) { LOGR.log(Level.WARNING, "Failed to create DOMImplementationLS", e); } return factory; } }