From e545cb295b12e3ecb0d777b2f2b6a601c9b27387 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Sat, 14 Nov 2015 07:12:17 +0100 Subject: [PATCH] add: password reset with assurance. --- src/org/cacert/gigi/dbObjects/User.java | 9 ++-- .../cacert/gigi/pages/wot/AssuranceForm.java | 41 +++++++++++++++++++ .../cacert/gigi/pages/wot/AssuranceForm.templ | 4 ++ tests/org/cacert/gigi/TestPasswordReset.java | 17 ++++++++ 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/org/cacert/gigi/dbObjects/User.java b/src/org/cacert/gigi/dbObjects/User.java index fc7b1c3f..2bc1e563 100644 --- a/src/org/cacert/gigi/dbObjects/User.java +++ b/src/org/cacert/gigi/dbObjects/User.java @@ -495,7 +495,7 @@ public class User extends CertificateOwner { } public static User getResetWithToken(int id, String token) { - GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `memid` FROM `passwordResetTickets` WHERE `id`=? AND `token`=?"); + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `memid` FROM `passwordResetTickets` WHERE `id`=? AND `token`=? AND `used` IS NULL"); ps.setInt(1, id); ps.setString(2, token); GigiResultSet res = ps.executeQuery(); @@ -505,14 +505,17 @@ public class User extends CertificateOwner { return User.getById(res.getInt(1)); } - public void consumePasswordResetTicket(int id, String private_token, String newPassword) throws GigiApiException { - GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `private_token` FROM `passwordResetTickets` WHERE `id`=? AND `memid`=?"); + public synchronized void consumePasswordResetTicket(int id, String private_token, String newPassword) throws GigiApiException { + GigiPreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `private_token` FROM `passwordResetTickets` WHERE `id`=? AND `memid`=? AND `used` IS NULL"); ps.setInt(1, id); ps.setInt(2, getId()); try (GigiResultSet rs = ps.executeQuery()) { if ( !rs.next()) { throw new GigiApiException("Token not found... very bad."); } + ps = DatabaseConnection.getInstance().prepare("UPDATE `passwordResetTickets` SET `used` = CURRENT_TIMESTAMP WHERE `id`=?"); + ps.setInt(1, id); + ps.executeUpdate(); if (PasswordHash.verifyHash(private_token, rs.getString(1)) == null) { throw new GigiApiException("Private token does not match."); } diff --git a/src/org/cacert/gigi/pages/wot/AssuranceForm.java b/src/org/cacert/gigi/pages/wot/AssuranceForm.java index 4b6f9232..b1cfbae9 100644 --- a/src/org/cacert/gigi/pages/wot/AssuranceForm.java +++ b/src/org/cacert/gigi/pages/wot/AssuranceForm.java @@ -1,6 +1,8 @@ package org.cacert.gigi.pages.wot; +import java.io.IOException; import java.io.PrintWriter; +import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -11,11 +13,15 @@ import javax.servlet.http.HttpServletRequest; import org.cacert.gigi.GigiApiException; import org.cacert.gigi.dbObjects.Name; import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.email.Sendmail; import org.cacert.gigi.localisation.Language; import org.cacert.gigi.output.template.Form; import org.cacert.gigi.output.template.Template; import org.cacert.gigi.pages.Page; +import org.cacert.gigi.pages.PasswordResetPage; import org.cacert.gigi.util.Notary; +import org.cacert.gigi.util.RandomToken; +import org.cacert.gigi.util.ServerConstants; public class AssuranceForm extends Form { @@ -29,6 +35,8 @@ public class AssuranceForm extends Form { private String date = ""; + private String aword; + private static final Template templ; static { templ = new Template(AssuranceForm.class.getResource("AssuranceForm.templ")); @@ -56,6 +64,7 @@ public class AssuranceForm extends Form { res.put("dobFmt2", sdf2.format(assuree.getDoB())); res.put("location", location); res.put("date", date); + res.put("aword", aword); templ.output(out, l, res); } @@ -71,6 +80,15 @@ public class AssuranceForm extends Form { outputError(out, req, "You failed to check all boxes to validate" + " your adherence to the rules and policies of CAcert"); } + if ("1".equals(req.getParameter("passwordReset"))) { + aword = req.getParameter("passwordResetValue"); + if ("".equals(aword)) { + aword = null; + } + } else { + aword = null; + } + int pointsI = 0; String points = req.getParameter("points"); if (points == null || "".equals(points)) { @@ -88,6 +106,29 @@ public class AssuranceForm extends Form { } try { Notary.assure(Page.getUser(req), assuree, assureeName, dob, pointsI, location, req.getParameter("date")); + if (aword != null && !aword.equals("")) { + String systemToken = RandomToken.generateToken(32); + int id = assuree.generatePasswordResetTicket(Page.getUser(req), systemToken, aword); + try { + Language l = Language.getInstance(assuree.getPreferredLocale()); + StringBuffer body = new StringBuffer(); + body.append(l.getTranslation("Hi,") + "\n\n"); + body.append(l.getTranslation("A password reset was triggered. If you did a password reset by assurance, please enter your secret password using this form: \n")); + body.append(ServerConstants.getWwwHostNamePortSecure() + PasswordResetPage.PATH); + body.append("?id="); + body.append(id); + body.append("&token="); + body.append(URLEncoder.encode(systemToken, "UTF-8")); + body.append("\n"); + body.append("\n"); + body.append(l.getTranslation("Best regards")); + body.append("\n"); + body.append(l.getTranslation("CAcert.org Support!")); + Sendmail.getInstance().sendmail(assuree.getEmail(), "[CAcert.org] " + l.getTranslation("Password reset by assurance"), body.toString(), "support@cacert.org", null, null, null, null, false); + } catch (IOException e) { + e.printStackTrace(); + } + } return true; } catch (GigiApiException e) { e.format(out, Page.getLanguage(req)); diff --git a/src/org/cacert/gigi/pages/wot/AssuranceForm.templ b/src/org/cacert/gigi/pages/wot/AssuranceForm.templ index 1d5e3031..a69f3e3a 100644 --- a/src/org/cacert/gigi/pages/wot/AssuranceForm.templ +++ b/src/org/cacert/gigi/pages/wot/AssuranceForm.templ @@ -50,6 +50,10 @@
(Max. ) + + checked> + + diff --git a/tests/org/cacert/gigi/TestPasswordReset.java b/tests/org/cacert/gigi/TestPasswordReset.java index 384ec31d..18402106 100644 --- a/tests/org/cacert/gigi/TestPasswordReset.java +++ b/tests/org/cacert/gigi/TestPasswordReset.java @@ -27,6 +27,23 @@ public class TestPasswordReset extends ClientTest { assertNotNull(login(u.getEmail(), TEST_PASSWORD + "'")); } + @Test + public void testDoubleUse() throws IOException, GigiApiException { + User u2 = User.getResetWithToken(id, pub); + assertSame(u, u2); + assertNotNull(login(u.getEmail(), TEST_PASSWORD)); + u2.consumePasswordResetTicket(id, priv, TEST_PASSWORD + "'"); + assertEquals("", login(u.getEmail(), TEST_PASSWORD)); + assertNotNull(login(u.getEmail(), TEST_PASSWORD + "'")); + try { + u2.consumePasswordResetTicket(id, priv, TEST_PASSWORD + "''"); + fail("Exception expected."); + } catch (GigiApiException e) { + // expected + } + assertNotNull(login(u.getEmail(), TEST_PASSWORD + "'")); + } + @Test public void testInternalWrongTk() throws IOException, GigiApiException { User u2 = User.getResetWithToken(id, pub + "'"); -- 2.39.2