/** * Handles user accounts. * * @author Edward Y. Chen * @since 02/08/2013 */ package edu.mssm.pharm.maayanlab.Enrichr; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Properties; import java.util.Set; import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.hibernate.Criteria; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Restrictions; import edu.mssm.pharm.maayanlab.common.core.FileUtils; import edu.mssm.pharm.maayanlab.common.web.HibernateUtil; import edu.mssm.pharm.maayanlab.common.web.JSONify; import edu.mssm.pharm.maayanlab.common.math.HashFunctions; @WebServlet(urlPatterns = {"/account", "/login", "/register", "/forgot", "/reset", "/status", "/logout", "/contribute", "/delete"}) public class Account extends HttpServlet { private static final long serialVersionUID = 19776535963654466L; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("application/json"); JSONify json = EnrichrContext.getJSONConverter(); // Get special JSON converter to handle serialization of List model HttpSession httpSession = request.getSession(); User user = (User) httpSession.getAttribute("user"); if (request.getServletPath().equals("/logout")) { httpSession.removeAttribute("user"); response.sendRedirect(""); // Redirect to home page return; } if (user == null) { json.add("user", ""); // Front-end will deal with empty user } else { json.add("user", user.getEmail()); // Common info shared between any GET json.add("firstname", user.getFirst()); // Get user lists if (request.getServletPath().equals("/account")) { SessionFactory sf = HibernateUtil.getSessionFactory(); Session session = null; try { session = sf.getCurrentSession(); } catch (HibernateException he) { session = sf.openSession(); } session.beginTransaction(); session.update(user); json.add("lastname", user.getLast()); // Additional info on account page json.add("institution", user.getInstitute()); json.add("lists", user.getLists()); session.getTransaction().commit(); session.close(); } } json.write(response.getWriter()); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // All form submissions are ajax and take json responses response.setContentType("application/json"); JSONify json = new JSONify(); // Create database session SessionFactory sf = HibernateUtil.getSessionFactory(); Session dbSession = sf.openSession(); dbSession.beginTransaction(); // All these tasks require db if (request.getServletPath().equals("/login")) login(request, response, dbSession, json); else if (request.getServletPath().equals("/register")) register(request, response, dbSession, json); else if (request.getServletPath().equals("/forgot")) forgot(request, response, dbSession, json); else if (request.getServletPath().equals("/reset")) reset(request, response, dbSession, json); else if (request.getServletPath().equals("/account")) modify(request, response, dbSession, json); else if (request.getServletPath().equals("/contribute")) contribute(request, response, dbSession, json); else if (request.getServletPath().equals("/delete")) delete(request, response, dbSession, json); // Close database session and write out json dbSession.getTransaction().commit(); dbSession.close(); json.write(response.getWriter()); } // Handle account registration private void register(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { // Check for existing email String email = request.getParameter("email"); Criteria criteria = dbSession.createCriteria(User.class) .add(Restrictions.eq("email", email).ignoreCase()); User user = (User) criteria.uniqueResult(); if (user != null) { // If exists, display error json.add("message", "The email you entered is already registered."); } else { // Else, create user User newUser = new User(email, request.getParameter("password"), request.getParameter("firstname"), request.getParameter("lastname"), request.getParameter("institution")); dbSession.save(newUser); request.getSession().setAttribute("user", newUser); json.add("redirect", "index.html"); } } // Handle account login private void login(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { String email = request.getParameter("email"); String password = request.getParameter("password"); Criteria criteria = dbSession.createCriteria(User.class) .add(Restrictions.eq("email", email).ignoreCase()); User user = (User) criteria.uniqueResult(); if (user == null) { // If user doesn't exist, display error json.add("message", "The email you entered does not belong to a registered user."); } else if (user.checkPassword(password)) { user.updateAccessed(); // Sets accessed field to NULL so db can auto-update it based on access timestamp dbSession.update(user); request.getSession().setAttribute("user", user); json.add("redirect", "account.html"); // Login successful, redirect } else { // If password is incorrect, display error json.add("message", "The password you entered is incorrect."); } } // Handle password reset request private void forgot(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { String email = request.getParameter("email"); Criteria criteria = dbSession.createCriteria(User.class) .add(Restrictions.eq("email", email).ignoreCase()); User user = (User) criteria.uniqueResult(); if (user == null) { // If user doesn't exist, display error json.add("message", "The email you entered does not belong to a registered user."); } else { // One day token for password reset SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); String token = user.getEmail() + user.getSalt() + formatter.format(Calendar.getInstance().getTime()); token = HashFunctions.md5(token); Properties props = new Properties(); props.put("mail.smtp.host", "mail.maayanlab.net"); props.put("mail.smtp.auth", "true"); javax.mail.Session mailSession = javax.mail.Session.getInstance(props, new Authenticator() { @Override public PasswordAuthentication getPasswordAuthentication() { // TODO: pull this out to a settings file so not hardcoded return new PasswordAuthentication("amp@maayanlab.net", "1amp1"); } }); // Web app has no concept of domain so link is hardcoded MimeMessage message = new MimeMessage(mailSession); try { message.setFrom(new InternetAddress("Enrichr@amp.pharm.mssm.edu")); message.setRecipient(Message.RecipientType.TO, new InternetAddress(email)); message.setSubject("Enrichr Password Reset"); message.setSentDate(new Date()); message.setText("Reset your password at http://amp.pharm.mssm.edu/Enrichr/reset.html?user=" + email + "&token=" + token + ".\n\nIf you did not request this password reset, please ignore this email."); Transport.send(message); } catch (MessagingException e) { e.printStackTrace(); } json.add("message", "Password reset request sent! Check your email for a reset link."); } } // Handle password reset private void reset(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { String email = request.getParameter("email"); Criteria criteria = dbSession.createCriteria(User.class) .add(Restrictions.eq("email", email).ignoreCase()); User user = (User) criteria.uniqueResult(); if (user == null) { // If user doesn't exist, display error json.add("message", "The email you entered does not belong to a registered user."); } else { // Generate today and yesterday's tokens just in case they generate close to midnight SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); Calendar calendar = Calendar.getInstance(); String today = user.getEmail() + user.getSalt() + formatter.format(calendar.getTime()); calendar.add(Calendar.DATE, -1); String yesterday = user.getEmail() + user.getSalt() + formatter.format(calendar.getTime()); String token = request.getParameter("token"); if (!token.equalsIgnoreCase(HashFunctions.md5(today)) && !token.equalsIgnoreCase(HashFunctions.md5(yesterday))) { json.add("message", "Your reset token has expired. Please request a new one."); } else { user.updatePassword(request.getParameter("password")); dbSession.update(user); json.add("redirect", "login.html"); // Direct them to login } } } // Handle modification of account info private void modify(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { User user = (User) request.getSession().getAttribute("user"); String password = request.getParameter("password"); if (user == null) { // If idled to logout, redirect them to login json.add("redirect", "login.html"); } else if (user.checkPassword(password)) { // Check password in order to change boolean changed = user.updateUser(request.getParameter("email"), request.getParameter("newpassword"), request.getParameter("firstname"), request.getParameter("lastname"), request.getParameter("institution")); if (changed) { // Don't update unless necessary, save db write dbSession.update(user); json.add("message", "Changes saved."); } else { json.add("message", "No changes found."); } } else { json.add("message", "The password you entered is incorrect."); } } // Handle contributing list to crowdsourced lists private void contribute(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { HttpSession httpSession = request.getSession(); User user = (User) httpSession.getAttribute("user"); if (user == null) { // If idled to logout, redirect them to login json.add("redirect", "login.html"); return; } dbSession.update(user); // Make sure the user does own the list and list isn't already shared String sharedListEncodedId = request.getParameter("listId"); int sharedListId = Shortener.decode(sharedListEncodedId); List list = (List) dbSession.get(List.class, sharedListId); SharedList sharedList = (SharedList) dbSession.get(SharedList.class, sharedListId); if (sharedList != null) { json.add("message", "You've already contributed that list."); } else if (list != null && list.getUser().equals(user)) { sharedList = new SharedList(sharedListId, user, request.getParameter("description"), Boolean.parseBoolean(request.getParameter("privacy"))); // Look for list on path String resourceUrl = Enrichr.RESOURCE_PATH + sharedListEncodedId + ".txt"; if (!(new File(resourceUrl)).isFile()) { throw new IOException("List doesn't exist"); // Somehow file is missing? } // Read file ArrayList<String> input = FileUtils.readResource(resourceUrl); if (input.get(0).startsWith("#")) // If input line starts with comment input.remove(0).replaceFirst("#", ""); // Add each gene in list Set<SharedGene> sharedGenes = sharedList.getSharedGenes(); for (String gene : input) { sharedGenes.add(new SharedGene(sharedList, gene)); } dbSession.save(sharedList); json.add("listId", sharedListEncodedId); } else { json.add("message", "The list doesn't belong to you."); } } // Handle deleting of lists private void delete(HttpServletRequest request, HttpServletResponse response, Session dbSession, JSONify json) throws IOException { HttpSession httpSession = request.getSession(); User user = (User) httpSession.getAttribute("user"); if (user == null) { // If idled to logout, redirect them to login json.add("redirect", "login.html"); return; } dbSession.update(user); String listEncodedId = request.getParameter("listId"); List list = (List) dbSession.get(List.class, Shortener.decode(listEncodedId)); if (list != null && list.getUser().equals(user)) { // Make sure list exists and owner owns the list user.getLists().remove(list); dbSession.update(user); String resourceUrl = Enrichr.RESOURCE_PATH + listEncodedId + ".txt"; File file = new File(resourceUrl); if (!file.delete()) { throw new IOException("Delete failed"); } } json.add("redirect", "account.html"); // Refresh account page } // Static function to commit new lists to the user so the Enrichr class doesn't make any db calls static void updateUser(User user) { SessionFactory sf = HibernateUtil.getSessionFactory(); Session session = null; try { session = sf.getCurrentSession(); } catch (HibernateException he) { session = sf.openSession(); } session.beginTransaction(); session.update(user); session.getTransaction().commit(); session.close(); } }