From 607d6b144ce00d31b29760841224af015e633bbd Mon Sep 17 00:00:00 2001 From: Chris Mailloux Date: Fri, 10 Nov 2017 18:08:51 -0500 Subject: [PATCH 01/53] Add passay library for password requirements validation --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 5217192..71b80e6 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ dependencies { compile group: 'org.mindrot', name: 'jbcrypt', version: '0.4' compile 'com.github.v-ladynev:fluent-hibernate-core:0.3.1' compile group: 'com.h2database', name: 'h2', version: '1.4.196' + compile group: 'org.passay', name: 'passay', version: '1.3.0' } // This task builds a jar that will make our project distributable. From cbbdc41435d0691457f0846d76f4ab030e0f7378 Mon Sep 17 00:00:00 2001 From: Chris Mailloux Date: Fri, 10 Nov 2017 18:11:15 -0500 Subject: [PATCH 02/53] Implement password requirements checker --- .../model/services/PasswordValidatorUtil.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java diff --git a/src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java b/src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java new file mode 100644 index 0000000..a7d19c9 --- /dev/null +++ b/src/main/java/com/gitrekt/resort/model/services/PasswordValidatorUtil.java @@ -0,0 +1,19 @@ +package com.gitrekt.resort.model.services; + +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.LengthRule; +import org.passay.PasswordValidator; + +public class PasswordValidatorUtil { + + public static final PasswordValidator validator = new PasswordValidator( + // 60 is around the max length of BCrypt passwords anyways + new LengthRule(10,60), + new CharacterRule(EnglishCharacterData.UpperCase, 1), + new CharacterRule(EnglishCharacterData.LowerCase, 1), + new CharacterRule(EnglishCharacterData.Digit, 1), + new CharacterRule(EnglishCharacterData.Special, 1) + ); + +} From 81c27b85ae570b86bc9d915c382cfb01507d95c0 Mon Sep 17 00:00:00 2001 From: Chris Mailloux Date: Fri, 10 Nov 2017 18:11:45 -0500 Subject: [PATCH 03/53] Implement employee password reset --- ...ResetEmployeePasswordDialogController.java | 153 ++++++++++++++++-- .../resort/controller/ScreenManager.java | 5 +- .../StaffAccountsScreenController.java | 28 +++- .../resort/model/entities/Employee.java | 4 +- .../fxml/ResetEmployeePasswordDialog.fxml | 45 ++---- 5 files changed, 183 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java b/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java index 3c7caed..c43d26b 100644 --- a/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java +++ b/src/main/java/com/gitrekt/resort/controller/ResetEmployeePasswordDialogController.java @@ -1,12 +1,20 @@ package com.gitrekt.resort.controller; +import com.gitrekt.resort.model.entities.Employee; +import com.gitrekt.resort.model.services.EmployeeService; +import com.gitrekt.resort.model.services.PasswordValidatorUtil; import java.net.URL; import java.util.ResourceBundle; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.stage.Stage; +import javax.persistence.EntityNotFoundException; +import org.passay.PasswordData; +import org.passay.RuleResult; +import org.passay.RuleResultDetail; /** * Controller class for the dialog in which employees reset their password. @@ -20,43 +28,162 @@ public class ResetEmployeePasswordDialogController implements Initializable { private Button confirmButton; @FXML - private TextField authorizingManagerIdField; + private TextField newPasswordField; @FXML - private TextField authorizingManagerPasswordField; + private TextField confirmPasswordField; @FXML - private TextField confirmEmployeeIdField; + private Label errorLabel; @FXML - private TextField newPasswordField; + private Label lengthLabel; @FXML - private TextField confirmPasswordField; + private Label specialCharLabel; + + @FXML + private Label mixedCaseLabel; + + @FXML + private Label numberLabel; + + private Long employeeId; /** * Initializes the controller class. */ @Override public void initialize(URL url, ResourceBundle rb) { - // TODO + // Configure text change listeners for the two fields + newPasswordField.setOnKeyPressed( + e -> onNewPasswordFieldUpdated() + ); + confirmPasswordField.setOnKeyPressed(e -> + onConfirmPasswordFieldUpdated() + ); } - public void onCancelButtonClicked() { + /** + * Called to initialize the requisite data for the dialog. + * + * @param employeeId The employee id of the employee to reset the password + * for. + */ + public void setEmployeeId(Long employeeId) { + this.employeeId = employeeId; + } + + @FXML + private void onCancelButtonClicked() { Stage dialogStage = (Stage) cancelButton.getScene().getWindow(); dialogStage.close(); } - public void onConfirmButtonClicked() { - // TODO + @FXML + private void onConfirmButtonClicked() { + if(validatePasswords()) { + String password = newPasswordField.getText(); + changePassword(password); + closeDialog(); + } + } + + private void onConfirmPasswordFieldUpdated() { + validatePasswords(); } - public void onConfirmPasswordFieldUpdated() { - // TODO + private void onNewPasswordFieldUpdated() { + validatePasswords(); } - public void onNewPasswordFieldUpdated() { - // TODO + /** + * In addition to determining whether the passwords are valid, this method + * also handles the ui cues that let the user know what is wrong with their + * password. Maybe not the best instance of the SRP, but it's simple enough + * in practice. + * + * @return True if the passwords entered are valid. + */ + private boolean validatePasswords() { + boolean result = true; + + String newPassword = newPasswordField.getText(); + String matchingPassword = confirmPasswordField.getText(); + + // Hide any already showing errors + errorLabel.setVisible(false); + lengthLabel.getStyleClass().remove("validationErrorText"); + specialCharLabel.getStyleClass().remove("validationErrorText"); + numberLabel.getStyleClass().remove("validationErrorText"); + mixedCaseLabel.getStyleClass().remove("validationErrorText"); + + if(newPassword.isEmpty() || matchingPassword.isEmpty()) { + errorLabel.setVisible(true); + errorLabel.setText("Fields cannot be empty"); + result = false; + } + + if(!newPassword.equals(matchingPassword)) { + errorLabel.setVisible(true); + errorLabel.setText("Passwords don't match"); + result = false; + } + + RuleResult ruleResult = PasswordValidatorUtil.validator + .validate(new PasswordData(newPassword)); + + if(!ruleResult.isValid()) { + result = false; + } + + // Update the requirements labels + for(RuleResultDetail d : ruleResult.getDetails()) { + String errorCode = d.getErrorCode(); + switch(errorCode) { + case "TOO_SHORT": + lengthLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_LOWERCASE": + case "INSUFFICIENT_UPPERCASE": + mixedCaseLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_DIGIT": + numberLabel.getStyleClass().add("validationErrorText"); + break; + case "INSUFFICIENT_SPECIAL": + specialCharLabel.getStyleClass().add("validationErrorText"); + break; + } + } + + return result; + } + + /** + * Handles the actual act of changing the user's password. + * + * @param newPassword The password, which should be already validated. + */ + private void changePassword(String newPassword) { + // Ensure that we have an employee to change the password for + if(this.employeeId == null) { + throw new IllegalStateException("Must initialize employeeId"); + } + + EmployeeService employeeService = new EmployeeService(); + Employee employee = employeeService.getEmployeeById(this.employeeId); + try { + employee.setPassword(newPassword); + employeeService.updateEmployee(employee); + } catch (EntityNotFoundException e) { + // TODO + } + } + + private void closeDialog() { + Stage dialogStage = (Stage) this.lengthLabel.getScene().getWindow(); + dialogStage.close(); } } diff --git a/src/main/java/com/gitrekt/resort/controller/ScreenManager.java b/src/main/java/com/gitrekt/resort/controller/ScreenManager.java index 0f95b49..c317816 100644 --- a/src/main/java/com/gitrekt/resort/controller/ScreenManager.java +++ b/src/main/java/com/gitrekt/resort/controller/ScreenManager.java @@ -48,8 +48,10 @@ public void initialize(Stage mainStage) { /** * @param fxmlPath The path to the FXML file of the screen to switch to. + * + * @return The FXML controller of the new scene. */ - public void switchToScreen(String fxmlPath) { + public Object switchToScreen(String fxmlPath) { Parent newScreenRoot; try { @@ -61,6 +63,7 @@ public void switchToScreen(String fxmlPath) { mainStage.getScene().setRoot(newScreenRoot); mainStage.setMaximized(true); + return fxmlLoader.getController(); } /** diff --git a/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java b/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java index 298f910..b5d184b 100644 --- a/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java +++ b/src/main/java/com/gitrekt/resort/controller/StaffAccountsScreenController.java @@ -9,6 +9,8 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; import javafx.scene.image.Image; import javafx.stage.Modality; import javafx.stage.Stage; @@ -30,6 +32,18 @@ public class StaffAccountsScreenController implements Initializable { @FXML private Button addNewEmployeeButton; + @FXML + private TableView staffAccountTableView; + + @FXML + private TableColumn idColumn; + + @FXML + private TableColumn nameColumn; + + @FXML + private TableColumn managerColumn; + private final Image appLogo = new Image("images/Logo.png"); /** @@ -53,10 +67,12 @@ public void onRemoveEmployeeButtonClicked() { public void onResetEmployeePasswordButtonClicked() throws IOException { Stage dialogStage = new Stage(); dialogStage.getIcons().add(appLogo); - Parent dialogRoot = FXMLLoader.load( + FXMLLoader loader = new FXMLLoader( getClass().getResource("/fxml/ResetEmployeePasswordDialog.fxml") ); + Parent dialogRoot = loader.load(); Scene resetPasswordDialog = new Scene(dialogRoot); + dialogStage.setScene(resetPasswordDialog); dialogStage.initModality(Modality.APPLICATION_MODAL); dialogStage.initOwner( @@ -66,6 +82,11 @@ public void onResetEmployeePasswordButtonClicked() throws IOException { dialogStage.setTitle("Authentication Required"); dialogStage.centerOnScreen(); + ResetEmployeePasswordDialogController c; + c = (ResetEmployeePasswordDialogController) loader.getController(); + long employeeId = getSelectedEmployeeId(); + c.setEmployeeId(employeeId); + dialogStage.show(); } @@ -88,4 +109,9 @@ public void onAddNewEmployeeButtonClicked() throws IOException { dialogStage.show(); } + private long getSelectedEmployeeId() { + // TODO: REPLACE WITH REAL IMPLEMENTATION + return 1L; + } + } diff --git a/src/main/java/com/gitrekt/resort/model/entities/Employee.java b/src/main/java/com/gitrekt/resort/model/entities/Employee.java index eb29d7e..0237404 100644 --- a/src/main/java/com/gitrekt/resort/model/entities/Employee.java +++ b/src/main/java/com/gitrekt/resort/model/entities/Employee.java @@ -62,8 +62,8 @@ public void setManager(boolean isManager) { this.isManager = isManager; } - public void setHashedPassword(String hashedPassword){ - this.hashedPassword = hashedPassword; + public void setPassword(String plaintextPassword){ + encryptPassword(plaintextPassword); } private void encryptPassword(String plaintextPassword) { diff --git a/src/main/resources/fxml/ResetEmployeePasswordDialog.fxml b/src/main/resources/fxml/ResetEmployeePasswordDialog.fxml index 6b58681..86819c2 100644 --- a/src/main/resources/fxml/ResetEmployeePasswordDialog.fxml +++ b/src/main/resources/fxml/ResetEmployeePasswordDialog.fxml @@ -5,14 +5,13 @@ - - + @@ -23,33 +22,12 @@ - - - - - - - - - - - - - - - - - - - - -