From 515cc4c0dda2c47a143bcc4d475912c9b1b1fb17 Mon Sep 17 00:00:00 2001 From: Athou Date: Mon, 20 May 2013 21:53:13 +0200 Subject: [PATCH] recover password (wip) --- .../com/commafeed/backend/dao/UserDAO.java | 15 ++ .../com/commafeed/backend/model/User.java | 22 +++ .../services/FeedSubscriptionService.java | 8 +- .../backend/services/MailService.java | 38 +++-- .../backend/services/UserService.java | 9 + .../frontend/CommaFeedApplication.java | 7 + .../commafeed/frontend/pages/BasePage.java | 4 + .../pages/PasswordRecoveryCallbackPage.html | 22 +++ .../pages/PasswordRecoveryCallbackPage.java | 80 +++++++++ .../frontend/pages/PasswordRecoveryPage.html | 19 +++ .../frontend/pages/PasswordRecoveryPage.java | 71 ++++++++ .../frontend/pages/components/LoginPanel.html | 1 + .../frontend/pages/components/LoginPanel.java | 21 +++ .../frontend/rest/resources/FeedREST.java | 9 +- .../frontend/rest/resources/UserREST.java | 13 +- src/main/webapp/templates/admin.settings.html | 159 +++++++++++------- 16 files changed, 409 insertions(+), 89 deletions(-) create mode 100644 src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.html create mode 100644 src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.java create mode 100644 src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.html create mode 100644 src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.java diff --git a/src/main/java/com/commafeed/backend/dao/UserDAO.java b/src/main/java/com/commafeed/backend/dao/UserDAO.java index 937546c8..b67664cb 100644 --- a/src/main/java/com/commafeed/backend/dao/UserDAO.java +++ b/src/main/java/com/commafeed/backend/dao/UserDAO.java @@ -44,4 +44,19 @@ public class UserDAO extends GenericDAO { return user; } + public User findByEmail(String email) { + CriteriaQuery query = builder.createQuery(getType()); + Root root = query.from(getType()); + query.where(builder.equal(root.get(User_.email), email)); + TypedQuery q = em.createQuery(query); + + User user = null; + try { + user = q.getSingleResult(); + } catch (NoResultException e) { + user = null; + } + return user; + } + } diff --git a/src/main/java/com/commafeed/backend/model/User.java b/src/main/java/com/commafeed/backend/model/User.java index 8c7183a6..11dba1d4 100644 --- a/src/main/java/com/commafeed/backend/model/User.java +++ b/src/main/java/com/commafeed/backend/model/User.java @@ -43,6 +43,12 @@ public class User extends AbstractModel { @Temporal(TemporalType.TIMESTAMP) private Date lastLogin; + @Column(length = 40) + private String recoverPasswordToken; + + @Temporal(TemporalType.TIMESTAMP) + private Date recoverPasswordTokenDate; + @OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST) private Set roles = Sets.newHashSet(); @@ -110,4 +116,20 @@ public class User extends AbstractModel { this.apiKey = apiKey; } + public String getRecoverPasswordToken() { + return recoverPasswordToken; + } + + public void setRecoverPasswordToken(String recoverPasswordToken) { + this.recoverPasswordToken = recoverPasswordToken; + } + + public Date getRecoverPasswordTokenDate() { + return recoverPasswordTokenDate; + } + + public void setRecoverPasswordTokenDate(Date recoverPasswordTokenDate) { + this.recoverPasswordTokenDate = recoverPasswordTokenDate; + } + } diff --git a/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java b/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java index 566368c2..c8e32a1d 100644 --- a/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java +++ b/src/main/java/com/commafeed/backend/services/FeedSubscriptionService.java @@ -20,8 +20,10 @@ import com.google.api.client.util.Lists; @Stateless public class FeedSubscriptionService { + + @SuppressWarnings("serial") @ApplicationException - public class FeedSubscriptionException extends RuntimeException { + public static class FeedSubscriptionException extends RuntimeException { public FeedSubscriptionException(String msg) { super(msg); } @@ -51,10 +53,10 @@ public class FeedSubscriptionService { final String pubUrl = applicationSettingsService.get().getPublicUrl(); if (pubUrl == null) { throw new FeedSubscriptionException( - "Public URL of this CommaFeed is unset"); + "Public URL of this CommaFeed is unset"); } if (url.startsWith(pubUrl)) { - throw new RuntimeException( + throw new FeedSubscriptionException( "Could not subscribe to a feed from this CommaFeed instance"); } diff --git a/src/main/java/com/commafeed/backend/services/MailService.java b/src/main/java/com/commafeed/backend/services/MailService.java index 7fc8c151..d8efb24b 100644 --- a/src/main/java/com/commafeed/backend/services/MailService.java +++ b/src/main/java/com/commafeed/backend/services/MailService.java @@ -1,8 +1,10 @@ package com.commafeed.backend.services; +import java.io.Serializable; import java.util.Properties; import javax.inject.Inject; +import javax.mail.Authenticator; import javax.mail.Message; import javax.mail.PasswordAuthentication; import javax.mail.Session; @@ -10,35 +12,47 @@ import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.commafeed.backend.model.ApplicationSettings; import com.commafeed.backend.model.User; -public class MailService { +@SuppressWarnings("serial") +public class MailService implements Serializable { + + private static Logger log = LoggerFactory.getLogger(MailService.class); @Inject ApplicationSettingsService applicationSettingsService; - public void sendMail(User user, String subject, String content) throws Exception { + public void sendMail(User user, String subject, String content) + throws Exception { ApplicationSettings settings = applicationSettingsService.get(); - + final String username = settings.getSmtpUserName(); final String password = settings.getSmtpPassword(); - + + log.info(username); + log.info(password); + log.info("" + settings.isSmtpTls()); + log.info(settings.getSmtpHost()); + log.info("" + settings.getSmtpPort()); + String dest = user.getEmail(); Properties props = new Properties(); props.put("mail.smtp.auth", "true"); - props.put("mail.smtp.starttls.enable", settings.isSmtpTls()); + props.put("mail.smtp.starttls.enable", "" + settings.isSmtpTls()); props.put("mail.smtp.host", settings.getSmtpHost()); - props.put("mail.smtp.port", settings.getSmtpPort()); + props.put("mail.smtp.port", "" + settings.getSmtpPort()); - Session session = Session.getInstance(props, - new javax.mail.Authenticator() { - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); + Session session = Session.getInstance(props, new Authenticator() { + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); Message message = new MimeMessage(session); message.setFrom(new InternetAddress(username, "CommaFeed")); diff --git a/src/main/java/com/commafeed/backend/services/UserService.java b/src/main/java/com/commafeed/backend/services/UserService.java index 58cf0551..56f32ede 100644 --- a/src/main/java/com/commafeed/backend/services/UserService.java +++ b/src/main/java/com/commafeed/backend/services/UserService.java @@ -2,10 +2,13 @@ package com.commafeed.backend.services; import java.util.Calendar; import java.util.Collection; +import java.util.UUID; import javax.ejb.Stateless; import javax.inject.Inject; +import org.apache.commons.codec.digest.DigestUtils; + import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; @@ -94,4 +97,10 @@ public class UserService { userRoleDAO.delete(userRoleDAO.findAll(user)); userDAO.delete(user); } + + public String generateApiKey(User user) { + byte[] key = encryptionService.getEncryptedPassword(UUID.randomUUID() + .toString(), user.getSalt()); + return DigestUtils.sha1Hex(key); + } } diff --git a/src/main/java/com/commafeed/frontend/CommaFeedApplication.java b/src/main/java/com/commafeed/frontend/CommaFeedApplication.java index d3668d4f..0221dde6 100644 --- a/src/main/java/com/commafeed/frontend/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/frontend/CommaFeedApplication.java @@ -44,6 +44,8 @@ import com.commafeed.frontend.pages.GoogleImportRedirectPage; import com.commafeed.frontend.pages.HomePage; import com.commafeed.frontend.pages.LogoutPage; import com.commafeed.frontend.pages.NextUnreadRedirectPage; +import com.commafeed.frontend.pages.PasswordRecoveryCallbackPage; +import com.commafeed.frontend.pages.PasswordRecoveryPage; import com.commafeed.frontend.pages.WelcomePage; import com.commafeed.frontend.utils.exception.DisplayExceptionPage; @@ -66,8 +68,13 @@ public class CommaFeedApplication extends AuthenticatedWebApplication { mountPage("welcome", WelcomePage.class); mountPage("demo", DemoLoginPage.class); + + mountPage("recover", PasswordRecoveryPage.class); + mountPage("recover2", PasswordRecoveryCallbackPage.class); + mountPage("logout", LogoutPage.class); mountPage("error", DisplayExceptionPage.class); + mountPage("google/import/redirect", GoogleImportRedirectPage.class); mountPage(GoogleImportCallbackPage.PAGE_PATH, GoogleImportCallbackPage.class); diff --git a/src/main/java/com/commafeed/frontend/pages/BasePage.java b/src/main/java/com/commafeed/frontend/pages/BasePage.java index 7f7f5940..e1b30301 100644 --- a/src/main/java/com/commafeed/frontend/pages/BasePage.java +++ b/src/main/java/com/commafeed/frontend/pages/BasePage.java @@ -28,6 +28,7 @@ import com.commafeed.backend.model.ApplicationSettings; import com.commafeed.backend.model.User; import com.commafeed.backend.model.UserSettings; import com.commafeed.backend.services.ApplicationSettingsService; +import com.commafeed.backend.services.MailService; import com.commafeed.frontend.CommaFeedSession; import com.commafeed.frontend.utils.WicketUtils; import com.google.api.client.util.Maps; @@ -61,6 +62,9 @@ public abstract class BasePage extends WebPage { @Inject protected UserRoleDAO userRoleDAO; + + @Inject + MailService mailService; @Inject ApplicationSettingsService applicationSettingsService; diff --git a/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.html b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.html new file mode 100644 index 00000000..953526f4 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.html @@ -0,0 +1,22 @@ + + + +
+
+ +
+
+ Password: + +
+ Password: + +
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.java b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.java new file mode 100644 index 00000000..3744b15b --- /dev/null +++ b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryCallbackPage.java @@ -0,0 +1,80 @@ +package com.commafeed.frontend.pages; + +import java.util.Calendar; + +import javax.inject.Inject; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.PasswordTextField; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.validation.validator.StringValidator; + +import com.commafeed.backend.model.User; +import com.commafeed.backend.services.PasswordEncryptionService; +import com.commafeed.backend.services.UserService; +import com.commafeed.frontend.pages.components.BootstrapFeedbackPanel; +import com.commafeed.frontend.utils.exception.DisplayException; + +@SuppressWarnings("serial") +public class PasswordRecoveryCallbackPage extends BasePage { + + public static final String PARAM_EMAIL = "email"; + public static final String PARAM_TOKEN = "token"; + + @Inject + PasswordEncryptionService encryptionService; + + @Inject + UserService userService; + + public PasswordRecoveryCallbackPage(PageParameters params) { + String email = params.get(PARAM_EMAIL).toString(); + String token = params.get(PARAM_TOKEN).toString(); + + final User user = userDAO.findByEmail(email); + if (user == null) { + throw new DisplayException("email not found"); + } + if (user.getRecoverPasswordToken() == null + || !user.getRecoverPasswordToken().equals(token)) { + throw new DisplayException("invalid token"); + } + if (user.getRecoverPasswordTokenDate().before( + DateUtils.addDays(Calendar.getInstance().getTime(), -2))) { + throw new DisplayException("token expired"); + } + + final IModel password = new Model(); + final IModel confirm = new Model(); + add(new BootstrapFeedbackPanel("feedback")); + Form form = new Form("form") { + @Override + protected void onSubmit() { + String passwd = password.getObject(); + if (StringUtils.equals(passwd, confirm.getObject())) { + byte[] password = encryptionService.getEncryptedPassword( + passwd, user.getSalt()); + user.setPassword(password); + user.setApiKey(userService.generateApiKey(user)); + userDAO.update(user); + info("Password saved."); + } else { + error("Password do not match"); + } + } + }; + add(form); + form.add(new PasswordTextField("password", password).setResetPassword( + true).add(StringValidator.minimumLength(6))); + form.add(new PasswordTextField("confirm", confirm).setResetPassword( + true).add(StringValidator.minimumLength(6))); + + form.add(new BookmarkablePageLink("cancel", HomePage.class)); + + } +} diff --git a/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.html b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.html new file mode 100644 index 00000000..19940bfb --- /dev/null +++ b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.html @@ -0,0 +1,19 @@ + + + +
+
+ +
+
+ Email: + +
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.java b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.java new file mode 100644 index 00000000..14b16ee6 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/pages/PasswordRecoveryPage.java @@ -0,0 +1,71 @@ +package com.commafeed.frontend.pages; + +import java.util.Calendar; +import java.util.UUID; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.wicket.extensions.validation.validator.RfcCompliantEmailAddressValidator; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.RequiredTextField; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.commafeed.backend.model.User; +import com.commafeed.frontend.pages.components.BootstrapFeedbackPanel; + +@SuppressWarnings("serial") +public class PasswordRecoveryPage extends BasePage { + + private static Logger log = LoggerFactory + .getLogger(PasswordRecoveryPage.class); + + public PasswordRecoveryPage() { + + IModel email = new Model(); + add(new BootstrapFeedbackPanel("feedback")); + Form form = new Form("form", email) { + @Override + protected void onSubmit() { + super.onSubmit(); + User user = userDAO.findByEmail(getModelObject()); + if (user == null) { + error("Email not found."); + } else { + try { + user.setRecoverPasswordToken(DigestUtils.sha1Hex(UUID + .randomUUID().toString())); + user.setRecoverPasswordTokenDate(Calendar.getInstance() + .getTime()); + userDAO.update(user); + mailService.sendMail(user, + "CommaFeed - Password recovery", + buildEmailContent(user)); + info("Email sent."); + } catch (Exception e) { + log.error(e.getMessage(), e); + error("Cannot send email, please contact the staff."); + } + + } + } + + }; + add(form); + + form.add(new RequiredTextField("email", email) { + @Override + protected String getInputType() { + return "email"; + } + }.add(RfcCompliantEmailAddressValidator.getInstance())); + + form.add(new BookmarkablePageLink("cancel", HomePage.class)); + } + + private String buildEmailContent(User user) { + return "cc"; + } +} diff --git a/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.html b/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.html index e890f4ff..760e0c23 100644 --- a/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.html +++ b/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.html @@ -24,6 +24,7 @@

diff --git a/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.java b/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.java index 31a2ff14..86e387f1 100644 --- a/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.java +++ b/src/main/java/com/commafeed/frontend/pages/components/LoginPanel.java @@ -1,15 +1,36 @@ package com.commafeed.frontend.pages.components; +import javax.inject.Inject; + +import org.apache.commons.lang3.StringUtils; import org.apache.wicket.authroles.authentication.panel.SignInPanel; import org.apache.wicket.feedback.ContainerFeedbackMessageFilter; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; + +import com.commafeed.backend.services.ApplicationSettingsService; +import com.commafeed.frontend.pages.PasswordRecoveryPage; @SuppressWarnings("serial") public class LoginPanel extends SignInPanel { + @Inject + ApplicationSettingsService applicationSettingsService; + public LoginPanel(String id) { super(id); replace(new BootstrapFeedbackPanel("feedback", new ContainerFeedbackMessageFilter(this))); + Form form = (Form) get("signInForm"); + form.add(new BookmarkablePageLink("recover", + PasswordRecoveryPage.class){ + @Override + protected void onConfigure() { + super.onConfigure(); + String smtpHost = applicationSettingsService.get().getSmtpHost(); + setVisibilityAllowed(StringUtils.isNotBlank(smtpHost)); + } + }); } } diff --git a/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java b/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java index 3b4c9b03..d5d4f000 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/FeedREST.java @@ -226,12 +226,13 @@ public class FeedREST extends AbstractResourceREST { FeedInfo info = (FeedInfo) fetchFeed(url).getEntity(); try { feedSubscriptionService.subscribe(getUser(), info.getUrl(), - req.getTitle(), category); + req.getTitle(), category); } catch (Exception e) { log.info("Failed to subscribe to URL {}: {}", url, e.getMessage()); - return Response.status(Status.SERVICE_UNAVAILABLE).entity( - "Failed to subscribe to URL " + url + ": " + e.getMessage() - ).build(); + return Response + .status(Status.SERVICE_UNAVAILABLE) + .entity("Failed to subscribe to URL " + url + ": " + + e.getMessage()).build(); } return Response.ok(Status.OK).build(); } diff --git a/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java b/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java index a6775114..9f1fb854 100644 --- a/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java +++ b/src/main/java/com/commafeed/frontend/rest/resources/UserREST.java @@ -1,14 +1,11 @@ package com.commafeed.frontend.rest.resources; -import java.util.UUID; - import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.StringUtils; import com.commafeed.backend.StartupBean; @@ -120,10 +117,10 @@ public class UserREST extends AbstractResourceREST { byte[] password = encryptionService.getEncryptedPassword( request.getPassword(), user.getSalt()); user.setPassword(password); - user.setApiKey(generateKey(user)); + user.setApiKey(userService.generateApiKey(user)); } if (request.isNewApiKey()) { - user.setApiKey(generateKey(user)); + user.setApiKey(userService.generateApiKey(user)); } userDAO.update(user); return Response.ok().build(); @@ -140,10 +137,4 @@ public class UserREST extends AbstractResourceREST { userService.unregister(getUser()); return Response.ok().build(); } - - private String generateKey(User user) { - byte[] key = encryptionService.getEncryptedPassword(UUID.randomUUID() - .toString(), user.getSalt()); - return DigestUtils.sha1Hex(key); - } } diff --git a/src/main/webapp/templates/admin.settings.html b/src/main/webapp/templates/admin.settings.html index de3bef69..ef26fc53 100644 --- a/src/main/webapp/templates/admin.settings.html +++ b/src/main/webapp/templates/admin.settings.html @@ -10,66 +10,107 @@
-
- -
- +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + Requires restart +
+
+
+ +
+ +
+
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- - Requires restart -
-
-
- -
- +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+