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 @@
+
+
+
+
+
+

+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+

+
+
+
+
+
+
+
\ 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 @@