]> WPIA git - gigi.git/commitdiff
Implement and test change password form.
authorFelix Dörre <felix@dogcraft.de>
Thu, 24 Jul 2014 20:15:18 +0000 (22:15 +0200)
committerFelix Dörre <felix@dogcraft.de>
Thu, 24 Jul 2014 23:44:40 +0000 (01:44 +0200)
src/org/cacert/gigi/GigiApiException.java
src/org/cacert/gigi/User.java
src/org/cacert/gigi/pages/account/ChangeForm.java
src/org/cacert/gigi/pages/account/ChangePasswordPage.java
src/org/cacert/gigi/util/PasswordStrengthChecker.java
tests/org/cacert/gigi/pages/account/TestChangePassword.java [new file with mode: 0644]
tests/org/cacert/gigi/testUtils/ManagedTest.java

index 56d7f9addbdcbee58520a7d5c3dea8e90338fd2a..7b91d125fb1d200a4e1a822ab7dac70bbf3a693a 100644 (file)
@@ -2,17 +2,31 @@ package org.cacert.gigi;
 
 import java.io.PrintWriter;
 import java.sql.SQLException;
+import java.util.LinkedList;
 
 public class GigiApiException extends Exception {
        SQLException e;
-       String message;
+       LinkedList<String> messages = new LinkedList<>();
 
        public GigiApiException(SQLException e) {
+               super(e);
                this.e = e;
        }
 
        public GigiApiException(String message) {
-               this.message = message;
+               super(message);
+               messages.add(message);
+       }
+
+       public GigiApiException() {
+
+       }
+
+       public void mergeInto(GigiApiException e2) {
+               messages.addAll(e2.messages);
+               if (e == null) {
+                       e = e2.e;
+               }
        }
 
        public boolean isInternalError() {
@@ -20,13 +34,24 @@ public class GigiApiException extends Exception {
        }
 
        public void format(PrintWriter out, Language language) {
+               out.println("<div class='formError'>");
                if (isInternalError()) {
                        e.printStackTrace();
+                       out.print("<div>");
                        out.println(language.getTranslation("An internal error ouccured."));
-               } else {
-                       out.println(language.getTranslation(message));
+                       out.println("</div>");
+               }
+               for (String message : messages) {
+                       out.print("<div>");
+                       out.print(language.getTranslation(message));
+                       out.println("</div>");
                }
+               out.println("</div>");
+
+       }
 
+       public boolean isEmpty() {
+               return e == null && messages.size() == 0;
        }
 
 }
index d5b0a9043bce2ff0575a8d48ba9129e05f40a21a..10a10fd34283e8e03e3940af4bd3db17a736cc76 100644 (file)
@@ -8,6 +8,7 @@ import java.util.Calendar;
 
 import org.cacert.gigi.database.DatabaseConnection;
 import org.cacert.gigi.util.PasswordHash;
+import org.cacert.gigi.util.PasswordStrengthChecker;
 
 public class User {
 
@@ -116,6 +117,30 @@ public class User {
                id = DatabaseConnection.lastInsertId(query);
        }
 
+       public void changePassword(String oldPass, String newPass) throws GigiApiException {
+               try {
+                       PreparedStatement ps = DatabaseConnection.getInstance().prepare("SELECT `password` FROM users WHERE id=?");
+                       ps.setInt(1, id);
+                       ResultSet rs = ps.executeQuery();
+                       if (!rs.next()) {
+                               throw new GigiApiException("User not found... very bad.");
+                       }
+                       if (!PasswordHash.verifyHash(oldPass, rs.getString(1))) {
+                               throw new GigiApiException("Old password does not match.");
+                       }
+                       rs.close();
+                       PasswordStrengthChecker.assertStrongPassword(newPass, this);
+                       ps = DatabaseConnection.getInstance().prepare("UPDATE users SET `password`=? WHERE id=?");
+                       ps.setString(1, PasswordHash.hash(newPass));
+                       ps.setInt(2, id);
+                       if (ps.executeUpdate() != 1) {
+                               throw new GigiApiException("Password update failed.");
+                       }
+               } catch (SQLException e) {
+                       throw new GigiApiException(e);
+               }
+       }
+
        public boolean canAssure() throws SQLException {
                if (getAssurancePoints() < 100) {
                        return false;
index 2827af0b577c6606c03579d73108d966e3449dc7..5458b63d9f5ef9d94a9db148cccee44634194410 100644 (file)
@@ -5,13 +5,19 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.cacert.gigi.GigiApiException;
 import org.cacert.gigi.Language;
+import org.cacert.gigi.User;
 import org.cacert.gigi.output.Form;
 import org.cacert.gigi.output.template.Template;
+import org.cacert.gigi.pages.Page;
 
 public class ChangeForm extends Form {
-       public ChangeForm(HttpServletRequest hsr) {
+       User target;
+
+       public ChangeForm(HttpServletRequest hsr, User target) {
                super(hsr);
+               this.target = target;
        }
 
        private static Template t;
@@ -26,7 +32,28 @@ public class ChangeForm extends Form {
 
        @Override
        public boolean submit(PrintWriter out, HttpServletRequest req) {
-               return false;
+               String oldpassword = req.getParameter("oldpassword");
+               String p1 = req.getParameter("pword1");
+               String p2 = req.getParameter("pword2");
+               GigiApiException error = new GigiApiException();
+               if (oldpassword == null || p1 == null || p2 == null) {
+                       new GigiApiException("All fields are required.").format(out, Page.getLanguage(req));
+                       return false;
+               }
+               if (!p1.equals(p2)) {
+                       new GigiApiException("New passwords do not match.").format(out, Page.getLanguage(req));
+                       return false;
+               }
+               try {
+                       target.changePassword(oldpassword, p1);
+               } catch (GigiApiException e) {
+                       error.mergeInto(e);
+               }
+               if (!error.isEmpty()) {
+                       error.format(out, Page.getLanguage(req));
+                       return false;
+               }
+               return true;
        }
 
 }
index 1919f544efd81ba52d8d5245912a1f4b0db2a00c..75fd6bb27812e8eccc4c52244f09a09b471a7c53 100644 (file)
@@ -6,6 +6,8 @@ import java.util.HashMap;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.cacert.gigi.output.Form;
+import org.cacert.gigi.pages.LoginPage;
 import org.cacert.gigi.pages.Page;
 
 public class ChangePasswordPage extends Page {
@@ -17,7 +19,14 @@ public class ChangePasswordPage extends Page {
 
        @Override
        public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-               new ChangeForm(req).output(resp.getWriter(), getLanguage(req), new HashMap<String, Object>());
+               new ChangeForm(req, LoginPage.getUser(req)).output(resp.getWriter(), getLanguage(req),
+                       new HashMap<String, Object>());
+       }
+
+       @Override
+       public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+               ChangeForm f = Form.getForm(req, ChangeForm.class);
+               f.submit(resp.getWriter(), req);
        }
 
 }
index cad2aba58106f21a872a41d32f80f5094174f4e6..3e4760bbf2b86c47fd53766fbbe5be89b26e0327 100644 (file)
@@ -2,6 +2,7 @@ package org.cacert.gigi.util;
 
 import java.util.regex.Pattern;
 
+import org.cacert.gigi.GigiApiException;
 import org.cacert.gigi.User;
 
 public class PasswordStrengthChecker {
@@ -70,6 +71,13 @@ public class PasswordStrengthChecker {
                return light;
        }
 
+       public static void assertStrongPassword(String pw, User u) throws GigiApiException {
+               if (checkpw(pw, u) < 3) {
+                       throw new GigiApiException("The Pass Phrase you submitted failed to contain enough"
+                               + " differing characters and/or contained words from" + " your name and/or email address.");
+               }
+       }
+
        private static boolean contained(String pw, String check) {
                if (check == null || check.equals("")) {
                        return false;
diff --git a/tests/org/cacert/gigi/pages/account/TestChangePassword.java b/tests/org/cacert/gigi/pages/account/TestChangePassword.java
new file mode 100644 (file)
index 0000000..5f09557
--- /dev/null
@@ -0,0 +1,126 @@
+package org.cacert.gigi.pages.account;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+
+import org.cacert.gigi.GigiApiException;
+import org.cacert.gigi.User;
+import org.cacert.gigi.testUtils.IOUtils;
+import org.cacert.gigi.testUtils.ManagedTest;
+import org.junit.Test;
+
+public class TestChangePassword extends ManagedTest {
+       User u = User.getById(createVerifiedUser("fn", "ln", createUniqueName() + "uni@example.org", TEST_PASSWORD));
+       String cookie;
+
+       public TestChangePassword() throws IOException {
+               cookie = login(u.getEmail(), TEST_PASSWORD);
+               assertTrue(isLoggedin(cookie));
+       }
+
+       @Test
+       public void testChangePasswordInternal() throws IOException, GigiApiException {
+               try {
+                       u.changePassword(TEST_PASSWORD + "wrong", TEST_PASSWORD + "v2");
+                       fail("Password change must not succeed if old password is wrong.");
+               } catch (GigiApiException e) {
+                       // expected
+               }
+               ;
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+               u.changePassword(TEST_PASSWORD, TEST_PASSWORD + "v2");
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+       }
+
+       @Test
+       public void testChangePasswordWeb() throws IOException {
+               String error = executeChangePassword("oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                       + "&pword1=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8")//
+                       + "&pword2=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8"));
+               assertNull(error);
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+       }
+
+       @Test
+       public void testChangePasswordWebOldWrong() throws IOException {
+               String error = executeChangePassword("oldpassword=a" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                       + "&pword1=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8")//
+                       + "&pword2=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8"));
+               assertNotNull(error);
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+       }
+
+       @Test
+       public void testChangePasswordWebNewWrong() throws IOException {
+               String error = executeChangePassword("oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                       + "&pword1=" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8")//
+                       + "&pword2=a" + URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8"));
+               assertNotNull(error);
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+       }
+
+       @Test
+       public void testChangePasswordWebNewEasy() throws IOException {
+               String error = executeChangePassword("oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                       + "&pword1=a&pword2=a");
+               assertNotNull(error);
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+       }
+
+       @Test
+       public void testChangePasswordWebMissingFields() throws IOException {
+               String np = URLEncoder.encode(TEST_PASSWORD + "v2", "UTF-8");
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+               String error = executeChangePassword("oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                       + "&pword1=" + np);
+               assertNotNull(error);
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+               error = executeChangePassword("oldpassword=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") //
+                       + "&pword2=" + np);
+               assertNotNull(error);
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+               error = executeChangePassword("pword1=" + np + "&pword2=" + np);
+               assertNotNull(error);
+               assertFalse(isLoggedin(login(u.getEmail(), TEST_PASSWORD + "v2")));
+               assertTrue(isLoggedin(login(u.getEmail(), TEST_PASSWORD)));
+
+       }
+
+       private String executeChangePassword(String query) throws IOException, MalformedURLException,
+               UnsupportedEncodingException {
+               URLConnection uc = new URL("https://" + getServerName() + ChangePasswordPage.PATH).openConnection();
+               uc.addRequestProperty("Cookie", cookie);
+               String csrf = getCSRF(uc);
+
+               uc = new URL("https://" + getServerName() + ChangePasswordPage.PATH).openConnection();
+               uc.addRequestProperty("Cookie", cookie);
+               uc.setDoOutput(true);
+               OutputStream os = uc.getOutputStream();
+               os.write(("csrf=" + URLEncoder.encode(csrf, "UTF-8") + "&" //
+               + query//
+               ).getBytes());
+               os.flush();
+               String error = fetchStartErrorMessage(IOUtils.readURL(uc));
+               return error;
+       }
+
+}
index 36ae2ca1802beec9cd821a1b0569e4f14b0941a8..20318a7f03eb35a46fcba5abad8bb7d714644ed0 100644 (file)
@@ -1,7 +1,6 @@
 package org.cacert.gigi.testUtils;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedReader;
@@ -196,7 +195,9 @@ public class ManagedTest {
        public String fetchStartErrorMessage(String d) throws IOException {
                String formFail = "<div class='formError'>";
                int idx = d.indexOf(formFail);
-               assertNotEquals(-1, idx);
+               if (idx == -1) {
+                       return null;
+               }
                String startError = d.substring(idx + formFail.length(), idx + 100).trim();
                return startError;
        }