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() {
}
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;
}
}
import org.cacert.gigi.database.DatabaseConnection;
import org.cacert.gigi.util.PasswordHash;
+import org.cacert.gigi.util.PasswordStrengthChecker;
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;
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;
@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;
}
}
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 {
@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);
}
}
import java.util.regex.Pattern;
+import org.cacert.gigi.GigiApiException;
import org.cacert.gigi.User;
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;
--- /dev/null
+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;
+ }
+
+}
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;
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;
}