package org.swellrt.server.box.servlet; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLDecoder; import java.text.MessageFormat; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.InternetAddress; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.NotImplementedException; import org.apache.velocity.Template; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.tools.ConversionUtils; import org.apache.velocity.tools.ToolManager; import org.waveprotocol.box.server.account.AccountData; import org.waveprotocol.box.server.account.HumanAccountData; import org.waveprotocol.box.server.authentication.SessionManager; import org.waveprotocol.box.server.persistence.AccountStore; import org.waveprotocol.box.server.persistence.PersistenceException; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.util.logging.Log; import com.google.inject.Inject; import com.typesafe.config.Config; public class EmailService extends BaseService { public static final String EMAIL = "email"; private static final String METHOD = "method"; private static final String SET = "set"; private static final String PASSWORD_RESET = "password-reset"; private static final String RECOVER_URL = "recover-url"; /* * Path that has the default templates and translations inside the classpath */ private static final String CLASSPATH_VELOCITY_PATH = "org/swellrt/server/velocity/"; private static final String RECOVER_PASSWORD_TEMPLATE = "RecoverPassword.vm"; private static final Log LOG = Log.get(EmailService.class); private static final String RECOVER_PASSWORD_BUNDLE = "EmailMessages"; private final AccountStore accountStore; private final String host; private final String from; private final String recoverPasswordTemplateName; private final VelocityEngine ve; private URLClassLoader loader; private Session mailSession; private ToolManager manager; private String recoverPasswordMessages; private EmailSender emailSender; private DecoupledTemplates decTemplates; @Inject public EmailService(SessionManager sessionManager, AccountStore accountStore, Config config, EmailSender emailSender, DecoupledTemplates decTemplates) { super(sessionManager); this.accountStore = accountStore; String velocityPath = config.getString("email.template_path"); this.host = config.getString("email.host"); this.from = config.getString("email.from_email_address"); this.emailSender = emailSender; this.decTemplates = decTemplates; Properties p = new Properties(); p.put("resource.loader", "file, class"); p.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); p.put("file.resource.loader.path", velocityPath); p.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); ve = new VelocityEngine(); ve.init(p); this.recoverPasswordTemplateName = ve.resourceExists("RecoverPassword.vm") ? RECOVER_PASSWORD_TEMPLATE : CLASSPATH_VELOCITY_PATH + RECOVER_PASSWORD_TEMPLATE; this.recoverPasswordMessages = new File(velocityPath + RECOVER_PASSWORD_BUNDLE + ".properties").exists() ? RECOVER_PASSWORD_BUNDLE : CLASSPATH_VELOCITY_PATH.replace("/", ".") + RECOVER_PASSWORD_BUNDLE; try { // based on http://stackoverflow.com/a/15654598/4928558 File file = new File(velocityPath); URL[] urls = {file.toURI().toURL()}; loader = new URLClassLoader(urls); } catch (MalformedURLException e) { LOG.warning("Error constructing classLoader for velocity internationalization resources:" + e.getMessage()); } Properties properties = new Properties(); // Get the default Session object. mailSession = Session.getDefaultInstance(properties, null); // Setup mail server properties.setProperty("mail.smtp.host", host); properties.setProperty("mail.smtp.from", from); manager = new ToolManager(false); manager.setVelocityEngine(ve); manager.configure("velocity-tools-config.xml"); } @Override public void execute(HttpServletRequest req, HttpServletResponse response) throws IOException { Enumeration<String> paramNames = req.getParameterNames(); if (!paramNames.hasMoreElements()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No parameters found!"); return; } else { String method = req.getParameter(METHOD); String email = req.getParameter(EMAIL); switch (method) { case SET: HumanAccountData account = sessionManager.getLoggedInAccount(req).asHuman(); if (account != null && account.getId().isAnonymous()) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "User is anonymous"); return; } try { account.setEmail(email); accountStore.putAccount(account); response.setStatus(HttpServletResponse.SC_OK); } catch (IllegalArgumentException t) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, t.getMessage()); } catch (PersistenceException e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } break; case PASSWORD_RESET: String recoverUrl = URLDecoder.decode(req.getParameter(RECOVER_URL), "UTF-8"); String idOrEmail = URLDecoder.decode(req.getParameter("id-or-email"), "UTF-8"); String emailAddress = ""; try { List<AccountData> accounts = null; try { accounts = accountStore.getAccountByEmail(idOrEmail); } catch (NotImplementedException e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } // try to find by username if not found by email if (accounts == null || accounts.isEmpty()) { AccountData acc = accountStore.getAccount(new ParticipantId(idOrEmail)); if (acc != null && !acc.getId().isAnonymous()) { accounts.add(acc); emailAddress = acc.asHuman().getEmail(); } } else { emailAddress = idOrEmail; } if (accounts != null && !accounts.isEmpty()) { for (AccountData a : accounts) { String userAddress = a.getId().getAddress(); String userName = a.getId().getAddress().split("@")[0]; double random = Math.random(); String token = Base64.encodeBase64URLSafeString((String.valueOf(random)).getBytes()); a.asHuman().setRecoveryToken(token); accountStore.putAccount(a); String recoverUrlCp = recoverUrl; if (recoverUrlCp.contains("$user-id")) { recoverUrlCp = recoverUrlCp.replaceAll("\\$user-id", userAddress); } if (recoverUrlCp.contains("$token")) { recoverUrlCp = recoverUrlCp.replaceAll("\\$token", token); } else { recoverUrlCp = recoverUrlCp + token; } Map<String, Object> ctx = new HashMap<String, Object>(); ctx.put("recoverUrl", recoverUrlCp); ctx.put("userName", userName); Locale locale = null; String localeStr = a.asHuman().getLocale(); if (localeStr == null) { locale = Locale.getDefault(); } else { locale = ConversionUtils.toLocale(localeStr); } Template t = decTemplates.getTemplateFromName(RECOVER_PASSWORD_TEMPLATE); ResourceBundle b = decTemplates.getBundleFromName(RECOVER_PASSWORD_BUNDLE, locale); String subject = MessageFormat.format(b.getString("restoreEmailSubject"), userName); String body = decTemplates.getTemplateMessage(t, RECOVER_PASSWORD_BUNDLE, ctx, locale); emailSender.send(new InternetAddress(emailAddress), subject, body); } } response.setStatus(HttpServletResponse.SC_OK); } catch (MessagingException mex) { LOG.severe("Unexpected messaging exception while sending email:" + mex.getMessage()); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } catch (PersistenceException e) { LOG.severe("Unexpected persistence exception while sending email:" + e.getMessage()); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } break; } } } }