Skip to content

Commit e64e9a8

Browse files
committed
Added force password changing and password complexity requirements
1 parent 6d59486 commit e64e9a8

32 files changed

+279
-34
lines changed

Diff for: assign-client/src/main/java/edu/rpi/aris/assign/client/Client.java

+107-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
package edu.rpi.aris.assign.client;
22

3+
import edu.rpi.aris.assign.DBUtils;
34
import edu.rpi.aris.assign.LibAssign;
45
import edu.rpi.aris.assign.MessageCommunication;
56
import edu.rpi.aris.assign.NetUtil;
6-
import edu.rpi.aris.assign.client.exceptions.AuthBanException;
7-
import edu.rpi.aris.assign.client.exceptions.InvalidAccessTokenException;
8-
import edu.rpi.aris.assign.client.exceptions.InvalidCredentialsException;
7+
import edu.rpi.aris.assign.client.exceptions.*;
98
import edu.rpi.aris.assign.client.model.Config;
109
import edu.rpi.aris.assign.message.ErrorMsg;
1110
import edu.rpi.aris.assign.message.Message;
11+
import edu.rpi.aris.assign.message.UserEditMsg;
1212
import javafx.application.Platform;
1313
import javafx.beans.binding.Bindings;
1414
import javafx.beans.property.SimpleObjectProperty;
1515
import javafx.geometry.Insets;
1616
import javafx.geometry.Pos;
1717
import javafx.scene.Node;
18+
import javafx.scene.Parent;
1819
import javafx.scene.control.*;
1920
import javafx.scene.layout.GridPane;
2021
import javafx.scene.layout.HBox;
2122
import javafx.scene.layout.Priority;
23+
import javafx.scene.layout.VBox;
2224
import javafx.util.Pair;
2325
import org.apache.commons.lang3.StringUtils;
2426
import org.apache.commons.lang3.tuple.ImmutableTriple;
@@ -415,10 +417,12 @@ private synchronized void doAuth(String user, String pass, boolean isAccessToken
415417
case NetUtil.AUTH_ERR:
416418
throw new IOException("The server encountered an error while attempting to authenticate this connection");
417419
default:
418-
if (res.startsWith(NetUtil.AUTH_OK)) {
419-
String accessToken = res.replaceFirst(NetUtil.AUTH_OK + " ", "");
420+
if (res.startsWith(NetUtil.AUTH_OK) || res.startsWith(NetUtil.AUTH_RESET)) {
421+
String accessToken = res.replaceFirst(res.startsWith(NetUtil.AUTH_OK) ? NetUtil.AUTH_OK : NetUtil.AUTH_RESET, "").trim();
420422
Config.ACCESS_TOKEN.setValue(URLDecoder.decode(accessToken, "UTF-8"));
421423
Platform.runLater(() -> Config.USERNAME.setValue(user));
424+
if (res.startsWith(NetUtil.AUTH_RESET))
425+
throw new PasswordResetRequiredException();
422426
} else
423427
throw new IOException(res);
424428
}
@@ -482,6 +486,28 @@ public <T extends Message> void processMessage(T message, ResponseHandler<T> res
482486
} catch (AuthBanException e) {
483487
AssignClient.getInstance().getMainWindow().displayErrorMsg("Temporary Ban", e.getMessage());
484488
responseHandler.onError(false, message);
489+
} catch (PasswordResetRequiredException e) {
490+
AssignClient.getInstance().getMainWindow().displayErrorMsg("Password Reset", e.getMessage(), true);
491+
disconnect();
492+
boolean retry = false;
493+
do {
494+
try {
495+
retry = false;
496+
resetPassword();
497+
responseHandler.onError(true, message);
498+
} catch (CancellationException e1) {
499+
responseHandler.onError(false, message);
500+
} catch (InvalidCredentialsException e1) {
501+
AssignClient.getInstance().getMainWindow().displayErrorMsg("Incorrect Password", "Your current password is incorrect", true);
502+
retry = true;
503+
} catch (WeakPasswordException e1) {
504+
AssignClient.getInstance().getMainWindow().displayErrorMsg("Weak Password", "Your new password does not meet the complexity requirements", true);
505+
retry = true;
506+
} catch (Throwable e1) {
507+
AssignClient.getInstance().getMainWindow().displayErrorMsg("Error", e1.getMessage());
508+
responseHandler.onError(false, message);
509+
}
510+
} while (retry);
485511
} catch (Throwable e) {
486512
logger.error("Error sending message", e);
487513
responseHandler.onError(false, message);
@@ -605,6 +631,74 @@ private Triple<String, String, Boolean> getCredentials() {
605631
return new ImmutableTriple<>(user, pass, isAccessToken);
606632
}
607633

634+
private void resetPassword() throws Exception {
635+
AtomicReference<Optional<Pair<String, String>>> result = new AtomicReference<>(null);
636+
Platform.runLater(() -> {
637+
Dialog<Pair<String, String>> dialog = new Dialog<>();
638+
dialog.setTitle("Reset Password");
639+
dialog.setHeaderText("Your password has expired.\n" +
640+
"Please reset your password\n\n" +
641+
DBUtils.COMPLEXITY_RULES);
642+
ButtonType loginButtonType = new ButtonType("Reset Password", ButtonBar.ButtonData.OK_DONE);
643+
dialog.getDialogPane().getButtonTypes().addAll(loginButtonType, ButtonType.CANCEL);
644+
GridPane grid = new GridPane();
645+
grid.setVgap(10);
646+
grid.setHgap(10);
647+
grid.setPadding(new Insets(20));
648+
PasswordField currentPass = new PasswordField();
649+
currentPass.setPromptText("Current Password");
650+
PasswordField newPassword = new PasswordField();
651+
newPassword.setPromptText("New Password");
652+
PasswordField retypePassword = new PasswordField();
653+
retypePassword.setPromptText("Retype Password");
654+
grid.add(new Label("Current Password:"), 0, 0);
655+
grid.add(currentPass, 1, 0);
656+
grid.add(new Label("New Password:"), 0, 1);
657+
grid.add(newPassword, 1, 1);
658+
grid.add(new Label("Retype Password:"), 0, 2);
659+
grid.add(retypePassword, 1, 2);
660+
Node loginButton = dialog.getDialogPane().lookupButton(loginButtonType);
661+
loginButton.disableProperty().bind(currentPass.textProperty().isEmpty().or
662+
(newPassword.textProperty().isEmpty()).or
663+
(retypePassword.textProperty().isEmpty()).or
664+
(newPassword.textProperty().isNotEqualTo(retypePassword.textProperty())).or
665+
(Bindings.createBooleanBinding(() -> !DBUtils.checkPasswordComplexity(Config.USERNAME.getValue(), newPassword.getText()), newPassword.textProperty())).or
666+
(currentPass.textProperty().isEqualTo(newPassword.textProperty())));
667+
dialog.getDialogPane().setContent(grid);
668+
dialog.setResultConverter(buttonType -> buttonType == ButtonType.CANCEL ? null : new Pair<>(currentPass.getText(), newPassword.getText()));
669+
currentPass.requestFocus();
670+
result.set(dialog.showAndWait());
671+
synchronized (result) {
672+
result.notify();
673+
}
674+
});
675+
synchronized (result) {
676+
try {
677+
result.wait();
678+
} catch (InterruptedException e) {
679+
e.printStackTrace();
680+
}
681+
}
682+
if (result.get() != null && result.get().isPresent()) {
683+
String oldPass = result.get().get().getKey();
684+
String newPass = result.get().get().getValue();
685+
UserEditMsg editMsg = new UserEditMsg(Config.USERNAME.getValue(), null, newPass, oldPass, true);
686+
try {
687+
connect();
688+
} catch (PasswordResetRequiredException ignored) {
689+
}
690+
Message res;
691+
try {
692+
res = editMsg.sendAndGet(this);
693+
} finally {
694+
disconnect();
695+
}
696+
if (!(res instanceof UserEditMsg))
697+
resetPassword();
698+
} else
699+
throw new CancellationException();
700+
}
701+
608702
public synchronized void disconnect() {
609703
disconnect(true);
610704
}
@@ -675,8 +769,14 @@ public synchronized String readMessage() throws IOException {
675769

676770
@Override
677771
public void handleErrorMsg(ErrorMsg msg) {
678-
// TODO: implement
679-
throw new RuntimeException("Not implemented: \n" + msg);
772+
switch (msg.getErrorType()) {
773+
case AUTH_FAIL:
774+
throw new InvalidCredentialsException();
775+
case AUTH_WEAK_PASS:
776+
throw new WeakPasswordException();
777+
default:
778+
throw new RuntimeException("Error: " + msg.getErrorType());
779+
}
680780
}
681781

682782
private boolean showCertWarning() {

Diff for: assign-client/src/main/java/edu/rpi/aris/assign/client/controller/AssignGui.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class AssignGui {
4646

4747
private AssignGui() {
4848
stage = new Stage();
49-
FXMLLoader loader = new FXMLLoader(ModuleRow.class.getResource("../view/assignment_window.fxml"));
49+
FXMLLoader loader = new FXMLLoader(ModuleRow.class.getResource("../view/assign_window.fxml"));
5050
loader.setController(this);
5151
Parent root;
5252
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package edu.rpi.aris.assign.client.controller;
2+
3+
public class AssignmentsGui {
4+
}

Diff for: assign-client/src/main/java/edu/rpi/aris/assign/client/exceptions/AuthBanException.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package edu.rpi.aris.assign.client.exceptions;
22

3-
public class AuthBanException extends Exception {
3+
public class AuthBanException extends RuntimeException {
44

55
public AuthBanException() {
66
super("Authentication failed. Your ip address has been temporarily banned");

Diff for: assign-client/src/main/java/edu/rpi/aris/assign/client/exceptions/InvalidAccessTokenException.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package edu.rpi.aris.assign.client.exceptions;
22

3-
public class InvalidAccessTokenException extends Exception {
3+
public class InvalidAccessTokenException extends RuntimeException {
44

55
public InvalidAccessTokenException() {
66
super("Access Token Expired");

Diff for: assign-client/src/main/java/edu/rpi/aris/assign/client/exceptions/InvalidCredentialsException.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package edu.rpi.aris.assign.client.exceptions;
22

3-
public class InvalidCredentialsException extends Exception {
3+
public class InvalidCredentialsException extends RuntimeException {
44

55
public InvalidCredentialsException() {
66
super("Invalid Credentials");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package edu.rpi.aris.assign.client.exceptions;
2+
3+
public class PasswordResetRequiredException extends RuntimeException {
4+
5+
public PasswordResetRequiredException() {
6+
super("Your password has expired and must be reset");
7+
}
8+
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package edu.rpi.aris.assign.client.exceptions;
2+
3+
public class WeakPasswordException extends RuntimeException {
4+
5+
public WeakPasswordException() {
6+
super("The given password does not meet the minimum password requirements");
7+
}
8+
9+
}

Diff for: assign-client/src/main/java/edu/rpi/aris/assign/client/guiold/AssignmentWindow.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public class AssignmentWindow {
8585
public AssignmentWindow() {
8686
clientInfo = new ClientInfo();
8787
problemList = new ProblemList();
88-
FXMLLoader loader = new FXMLLoader(AssignmentWindow.class.getResource("assignment_window.fxml"));
88+
FXMLLoader loader = new FXMLLoader(AssignmentWindow.class.getResource("assign_window.fxml"));
8989
loader.setController(this);
9090
Parent root;
9191
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package edu.rpi.aris.assign.client.model;
2+
3+
public class Assignments {
4+
}

Diff for: assign-client/src/main/resources/edu/rpi/aris/assign/client/view/assignment_window.fxml renamed to assign-client/src/main/resources/edu/rpi/aris/assign/client/view/assign_window.fxml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
</Menu>
3535
</menus>
3636
</MenuBar>
37-
<BorderPane stylesheets="@assignment_window.css">
37+
<BorderPane stylesheets="@assign_window.css">
3838
<top>
3939
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="CENTER">
4040
<Label fx:id="lblClass" alignment="CENTER" contentDisplay="CENTER" text="Class:">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<?import javafx.scene.control.TableColumn?>
4+
<?import javafx.scene.control.TableView?>
5+
<?import javafx.scene.layout.StackPane?>
6+
<?import javafx.scene.layout.VBox?>
7+
8+
9+
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
10+
<children>
11+
<VBox>
12+
<children>
13+
<TableView>
14+
<columns>
15+
<TableColumn prefWidth="75.0" text="Assignment" />
16+
<TableColumn prefWidth="75.0" text="Due Date" />
17+
<TableColumn prefWidth="75.0" text="Assigned By" />
18+
<TableColumn prefWidth="75.0" text="Completed" />
19+
</columns>
20+
<columnResizePolicy>
21+
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
22+
</columnResizePolicy>
23+
</TableView>
24+
</children>
25+
</VBox>
26+
<VBox />
27+
</children>
28+
</StackPane>

Diff for: assign-server/src/main/java/edu/rpi/aris/assign/server/ClientHandler.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import edu.rpi.aris.assign.message.ErrorMsg;
55
import edu.rpi.aris.assign.message.ErrorType;
66
import edu.rpi.aris.assign.message.Message;
7+
import edu.rpi.aris.assign.message.UserEditMsg;
78
import org.apache.commons.collections4.map.PassiveExpiringMap;
89
import org.apache.commons.lang3.tuple.Pair;
910
import org.apache.logging.log4j.LogManager;
@@ -130,7 +131,7 @@ private boolean verifyAuth() throws IOException {
130131
logger.info("Authenticating user: " + username);
131132
String pass = URLDecoder.decode(auth[3], "UTF-8");
132133
try (Connection connection = dbManager.getConnection();
133-
PreparedStatement statement = connection.prepareStatement("SELECT salt, password_hash, access_token, id, user_type FROM users WHERE username = ?;")) {
134+
PreparedStatement statement = connection.prepareStatement("SELECT salt, password_hash, access_token, id, user_type, force_reset FROM users WHERE username = ?;")) {
134135
statement.setString(1, username);
135136
try (ResultSet set = statement.executeQuery()) {
136137
if (set.next()) {
@@ -149,6 +150,7 @@ private boolean verifyAuth() throws IOException {
149150
updateUserType.executeUpdate();
150151
}
151152
}
153+
boolean forceReset = set.getBoolean(6);
152154
if (DBUtils.checkPass(pass, salt, savedHash)) {
153155
String access_token = generateAccessToken();
154156
MessageDigest digest = DBUtils.getDigest();
@@ -159,8 +161,8 @@ private boolean verifyAuth() throws IOException {
159161
updateAccessToken.setString(2, username);
160162
updateAccessToken.executeUpdate();
161163
}
162-
sendMessage(NetUtil.AUTH_OK + " " + URLEncoder.encode(access_token, "UTF-8"));
163-
user = new User(userId, username, userType);
164+
sendMessage((forceReset ? NetUtil.AUTH_RESET : NetUtil.AUTH_OK) + " " + URLEncoder.encode(access_token, "UTF-8"));
165+
user = new User(userId, username, userType, forceReset);
164166
return true;
165167
} else {
166168
if (auth[1].equals(NetUtil.AUTH_PASS)) {
@@ -218,7 +220,15 @@ private void messageWatch() {
218220
try (Connection connection = dbManager.getConnection()) {
219221
try {
220222
connection.setAutoCommit(false);
221-
ErrorType error = msg.processMessage(connection, user);
223+
ErrorType error;
224+
if (user.requireReset()) {
225+
if (msg instanceof UserEditMsg && ((UserEditMsg) msg).isChangePass() && user.username.equals(((UserEditMsg) msg).getUsername()))
226+
error = msg.processMessage(connection, user);
227+
else
228+
error = ErrorType.RESET_PASS;
229+
} else {
230+
error = msg.processMessage(connection, user);
231+
}
222232
if (error == null) {
223233
connection.commit();
224234
msg.send(this);

Diff for: assign-server/src/main/java/edu/rpi/aris/assign/server/DatabaseManager.java

+42-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
public class DatabaseManager {
2121

2222
private static final String[] tables = new String[]{"submission", "assignment", "problem", "user_class", "users", "class", "version"};
23-
private static final int DB_SCHEMA_VERSION = 4;
23+
private static final int DB_SCHEMA_VERSION = 6;
2424
private static Logger logger = LogManager.getLogger(DatabaseManager.class);
2525

2626
static {
@@ -88,13 +88,15 @@ private void createTables(Connection connection) throws SQLException {
8888
"user_type text," +
8989
"salt text," +
9090
"password_hash text," +
91-
"access_token text);");
91+
"access_token text," +
92+
"force_reset boolean);");
9293
statement.execute("CREATE TABLE IF NOT EXISTS class" +
9394
"(id serial PRIMARY KEY," +
9495
"name text);");
9596
statement.execute("CREATE TABLE IF NOT EXISTS user_class" +
9697
"(user_id integer," +
9798
"class_id integer," +
99+
"is_ta boolean" +
98100
"constraint uc_ufk foreign key (user_id) references users(id) on delete cascade," +
99101
"constraint uc_cfk foreign key (class_id) references class(id) on delete cascade);");
100102
statement.execute("CREATE TABLE IF NOT EXISTS problem" +
@@ -206,6 +208,44 @@ private void updateSchema3(Connection connection) throws SQLException {
206208
} finally {
207209
connection.setAutoCommit(autoCommit);
208210
}
211+
updateSchema4(connection);
212+
}
213+
214+
private void updateSchema4(Connection connection) throws SQLException {
215+
logger.info("Updating database schema to version 5");
216+
boolean autoCommit = connection.getAutoCommit();
217+
connection.setAutoCommit(false);
218+
try (Statement statement = connection.createStatement()) {
219+
statement.execute("ALTER TABLE user_class ADD COLUMN is_ta boolean;");
220+
statement.execute("UPDATE user_class SET is_ta=false;");
221+
statement.execute("UPDATE version SET version=5;");
222+
connection.commit();
223+
} catch (Throwable e) {
224+
connection.rollback();
225+
logger.error("An error occurred while updating the database schema and the changes were rolled back");
226+
throw e;
227+
} finally {
228+
connection.setAutoCommit(autoCommit);
229+
}
230+
updateSchema5(connection);
231+
}
232+
233+
private void updateSchema5(Connection connection) throws SQLException {
234+
logger.info("Updating database schema to version 6");
235+
boolean autoCommit = connection.getAutoCommit();
236+
connection.setAutoCommit(false);
237+
try (Statement statement = connection.createStatement()) {
238+
statement.execute("ALTER TABLE users ADD COLUMN force_reset boolean;");
239+
statement.execute("UPDATE users SET force_reset=false;");
240+
statement.execute("UPDATE version SET version=6;");
241+
connection.commit();
242+
} catch (Throwable e) {
243+
connection.rollback();
244+
logger.error("An error occurred while updating the database schema and the changes were rolled back");
245+
throw e;
246+
} finally {
247+
connection.setAutoCommit(autoCommit);
248+
}
209249
}
210250

211251
public Pair<String, String> createUser(String username, String password, UserType userType) throws SQLException {

0 commit comments

Comments
 (0)