package dbProcs;
import de.mkammerer.argon2.Argon2;
import de.mkammerer.argon2.Argon2Factory;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import utils.CountdownHandler;
import utils.InvalidCountdownStateException;
/**
* Used to add information to the Database
*
* This file is part of the Security Shepherd Project.
*
*
The Security Shepherd project is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
*
The Security Shepherd project is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
*
You should have received a copy of the GNU General Public License along with the Security
* Shepherd project. If not, see .
*
* @author Mark
*/
public class Setter {
private static final Logger log = LogManager.getLogger(Setter.class);
// TODO - Replace this with a new mobile/web/etc attribute in the modules table
public static final String webModuleCategoryHardcodedWhereClause =
new String(
"moduleCategory = 'CSRF'"
+ " OR moduleCategory = 'Failure to Restrict URL Access'"
+ " OR moduleCategory = 'Injection'"
+ " OR moduleCategory = 'Insecure Cryptographic Storage'"
+ " OR moduleCategory = 'Insecure Direct Object References'"
+ " OR moduleCategory = 'Poor Data Validation'"
+ " OR moduleCategory = 'Security Misconfigurations'"
+ " OR moduleCategory = 'Session Management'"
+ " OR moduleCategory = 'Unvalidated Redirects and Forwards'"
+ " OR moduleCategory = 'XSS'");
public static final String mobileModuleCategoryHardcodedWhereClause =
new String(
""
+ "moduleCategory = 'Mobile Broken Crypto'"
+ " OR moduleCategory = 'Mobile Content Provider'"
+ " OR moduleCategory = 'Mobile Data Leakage'"
+ " OR moduleCategory = 'Mobile Injection'"
+ " OR moduleCategory = 'Mobile Insecure Data Storage'"
+ " OR moduleCategory = 'Mobile Poor Authentication'"
+ " OR moduleCategory = 'Mobile Reverse Engineering'"
+ " OR moduleCategory = 'Mobile Security Decisions via Untrusted Input'");
/**
* Database procedure just adds this. So this method just prepares the statement
*
* @param ApplicationRoot
* @param className Class name
* @param classYear Year of the class in YY/YY. eg 11/12
* @return
*/
public static boolean classCreate(String ApplicationRoot, String className, String classYear) {
log.debug("*** Setter.classCreate ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmnt = conn.prepareCall("call classCreate(?, ?)")) {
log.debug("Preparing classCreate call");
callstmnt.setString(1, className);
callstmnt.setString(2, classYear);
log.debug("Executing classCreate");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.error("classCreate Failure: " + e.toString());
}
log.debug("*** END classCreate ***");
return result;
}
/**
* This method sets every module status to Closed.
*
* @param ApplicationRoot Current running director of the application
* @return Boolean result depicting success of statement
*/
public static boolean closeAllModules(String ApplicationRoot) {
log.debug("*** Setter.closeAllModules ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callstmt =
conn.prepareStatement("UPDATE modules SET moduleStatus = 'closed'")) {
callstmt.execute();
log.debug("All modules Set to closed");
result = true;
} catch (SQLException e) {
log.error("Could not close all modules: " + e.toString());
}
log.debug("*** END closeAllModules ***");
return result;
}
/**
* Used to increment bad submission counter in DB. DB will handle point deductions once the
* counter hits 40
*
* @param ApplicationRoot application running context
* @param userId user identifier to increment
* @return False if the statement fails to execute
*/
public static boolean incrementBadSubmission(String ApplicationRoot, String userId) {
log.debug("*** Setter.incrementBadSubmission ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callstmnt = conn.prepareCall("CALL userBadSubmission(?)")) {
log.debug("Prepairing bad Submission call");
callstmnt.setString(1, userId);
log.debug("Executing userBadSubmission statement on id '" + userId + "'");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.error("userBadSubmission Failure: " + e.toString());
result = false;
}
log.debug("*** END userBadSubmisison ***");
return result;
}
/**
* This method sets every module status to Open.
*
* @param ApplicationRoot Current running director of the application
* @param unsafe set whether to open all safe modules or all modules that are unsafe
* @return Boolean result depicting success of statement
*/
public static boolean openAllModules(String ApplicationRoot, boolean unsafe) {
log.debug("*** Setter.openAllModules ***");
boolean result = false;
String updateSql =
unsafe
? "UPDATE modules SET moduleStatus = 'open' WHERE isUnsafe = 1"
: "UPDATE modules SET moduleStatus = 'open' WHERE isUnsafe = 0";
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callstmt = conn.prepareStatement(updateSql)) {
callstmt.execute();
log.debug(unsafe ? "All unsafe modules set to open" : "All safe modules set to open");
result = true;
} catch (SQLException e) {
log.error("Could not open all modules: " + e.toString());
}
log.debug("*** END setModuleStatusOpen ***");
return result;
}
/**
* This is used to only open Mobile category levels
*
* @param ApplicationRoot Used to locate database properties file
* @return
*/
public static boolean openOnlyMobileCategories(String ApplicationRoot) {
log.debug("*** Setter.openOnlyMobileCategories ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
try (PreparedStatement prepstmt =
conn.prepareStatement(
"UPDATE modules SET moduleStatus = 'closed' WHERE "
+ webModuleCategoryHardcodedWhereClause)) {
prepstmt.execute();
log.debug("Web Levels have been closed");
}
try (PreparedStatement prepstmt =
conn.prepareStatement(
"UPDATE modules SET moduleStatus = 'open' WHERE "
+ mobileModuleCategoryHardcodedWhereClause)) {
prepstmt.execute();
log.debug("Mobile Levels have been opened");
}
result = true;
} catch (SQLException e) {
log.error("Could not only open Mobile Levels: " + e.toString());
}
log.debug("*** END openOnlyMobileCategories ***");
return result;
}
/**
* This is used to only open Mobile category levels
*
* @param ApplicationRoot Used to locate database properties file
* @param unsafe Used to track if the level is deemed unsafe or safe
* @return
*/
public static boolean openOnlyWebCategories(String ApplicationRoot, int unsafe) {
log.debug("*** Setter.openOnlyWebCategories ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
PreparedStatement prepstmt =
conn.prepareStatement(
"UPDATE modules SET moduleStatus = 'open' WHERE "
+ "("
+ webModuleCategoryHardcodedWhereClause
+ ")"
+ " AND isUnsafe = "
+ unsafe);
prepstmt.execute();
log.debug("Web Levels have been opened");
prepstmt =
conn.prepareStatement(
"UPDATE modules SET moduleStatus = 'closed' WHERE "
+ mobileModuleCategoryHardcodedWhereClause);
prepstmt.execute();
log.debug("Mobile Levels have been closed");
result = true;
} catch (SQLException e) {
log.error("Could not only open Web levels: " + e.toString());
}
log.debug("*** END openOnlyWebCategories ***");
return result;
}
/**
* Resets user bad submission counter to 0
*
* @param ApplicationRoot Application's running context
* @param userId User Identifier to reset
* @return
*/
public static boolean resetBadSubmission(String ApplicationRoot, String userId) {
log.debug("*** Setter.resetBadSubmission ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callstmnt = conn.prepareCall("CALL resetUserBadSubmission(?)")) {
log.debug("Prepairing resetUserBadSubmission call");
callstmnt.setString(1, userId);
log.debug("Executing resetUserBadSubmission statement on id '" + userId + "'");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.error("resetUserBadSubmission Failure: " + e.toString());
result = false;
}
log.debug("*** END resetBadSubmission ***");
return result;
}
/**
* This method converts the default database properties file at
* applicationRoot/WEB-INF/site.properties
*
* @param applicationRoot The directory that the server is actually in
* @param url The Url of the core Database
* @param userName The user name of the database user
* @param password The password of the database user
* @return Boolean value depicting the success of the method
*/
public static boolean setCoreDatabaseInfo(
String applicationRoot, String url, String userName, String password) {
userName = userName.toLowerCase();
try {
// Update Database Settings
File siteProperties = new File(applicationRoot + "/WEB-INF/database.properties");
DataOutputStream writer = new DataOutputStream(new FileOutputStream(siteProperties, false));
String theProperties =
new String("databaseConnectionURL=" + url + "\nDriverType=org.mariadb.jdbc.Driver");
writer.write(theProperties.getBytes());
writer.close();
// Update Core Schema Settings
siteProperties = new File(applicationRoot + "/WEB-INF/coreDatabase.properties");
writer = new DataOutputStream(new FileOutputStream(siteProperties, false));
theProperties =
new String(
"databaseConnectionURL=core"
+ "\ndatabaseUsername="
+ userName
+ "\ndatabasePassword="
+ password);
writer.write(theProperties.getBytes());
writer.close();
return true;
} catch (IOException e) {
log.error("Could not update Core Database Info: " + e.toString());
return false;
}
}
/**
* This method is used to store a CSRF Token for a specific user in the csrfChallengeSeven DB
* Schema. May not necessarily be a new CSRF token after running
*
* @param userId User Identifier
* @param csrfToken CSRF Token to add to the csrfChallengeFour DB Schema
* @param ApplicationRoot Running context of the application
* @return Returns current CSRF token for user for CSRF Ch4
*/
public static String setCsrfChallengeFourCsrfToken(
String userId, String csrfToken, String ApplicationRoot) {
log.debug("*** setCsrfChallengeFourToken ***");
try (Connection conn = Database.getChallengeConnection(ApplicationRoot, "csrfChallengeFour")) {
boolean tokenExists = false;
log.debug("Preparing setSsrfChallengeFourToken call");
try (PreparedStatement callstmnt =
conn.prepareStatement("SELECT csrfTokenscol FROM csrfTokens WHERE userId = ?")) {
callstmnt.setString(1, userId);
log.debug("Executing setCsrfChallengeFourToken");
try (ResultSet rs = callstmnt.executeQuery()) {
if (rs.next()) {
// Need to Update CSRF token rather than Insert
log.debug("CSRF for Challenge 4 already is set");
csrfToken = rs.getString(1); // overwrite token with DB Stored Entry
tokenExists = true;
} else {
log.debug("No CSRF token Found for Challenge 4... Creating");
}
}
}
String whatToDo = new String();
if (!tokenExists) {
whatToDo =
"INSERT INTO `csrfChallengeFour`.`csrfTokens` (`csrfTokenscol`, `userId`) VALUES (?,"
+ " ?)";
}
try (PreparedStatement callstmnt = conn.prepareStatement(whatToDo)) {
callstmnt.setString(1, csrfToken);
callstmnt.setString(2, userId);
callstmnt.execute();
}
} catch (SQLException e) {
log.error("CsrfChallenge4 TokenUpdate Failure: " + e.toString());
}
return csrfToken;
}
/**
* This method is used to store a CSRF Token for a specific user in the csrfChallengeSix DB Schema
*
* @param userId User Identifier
* @param csrfToken CSRF Token to add to the csrfChallengeSix DB Schema
* @param ApplicationRoot Running context of the application
* @return
*/
public static boolean setCsrfChallengeSevenCsrfToken(
String userId, String csrfToken, String ApplicationRoot) {
log.debug("*** setCsrfChallengeSevenToken ***");
boolean result = false;
try (Connection conn =
Database.getChallengeConnection(ApplicationRoot, "csrfChallengeEnumerateTokens")) {
boolean updateToken = false;
log.debug("Preparing setCsrfChallengeSevenToken call");
try (PreparedStatement prestmnt =
conn.prepareStatement("SELECT csrfTokenscol FROM csrfTokens WHERE userId = ?")) {
prestmnt.setString(1, userId);
log.debug("Executing setCsrfChallengeSevenToken");
try (ResultSet rs = prestmnt.executeQuery()) {
if (rs.next()) {
// Need to Update CSRF token rather than Insert
log.debug("CSRF token Found for Challenge 7... Updating");
updateToken = true;
} else {
log.debug("No CSRF token Found for Challenge 7... Creating");
}
}
}
String whatToDo;
if (updateToken) {
whatToDo =
"UPDATE `csrfChallengeEnumTokens`.`csrfTokens` SET csrfTokenscol = ? WHERE userId = ?";
} else {
whatToDo =
"INSERT INTO `csrfChallengeEnumTokens`.`csrfTokens` (`csrfTokenscol`, `userId`) VALUES"
+ " (?, ?)";
}
try (PreparedStatement prestmnt = conn.prepareStatement(whatToDo)) {
prestmnt.setString(1, csrfToken);
prestmnt.setString(2, userId);
log.debug("Executing: " + whatToDo);
prestmnt.execute();
result = true;
}
} catch (SQLException e) {
log.error("csrfChallenge7EnumTokens TokenUpdate Failure: " + e.toString());
}
return result;
}
/**
* This method is used to set the status of all modules in a category to open or closed.
*
* @param ApplicationRoot Used to locate database properties file
* @param moduleCategory The module category to open or closed
* @param openOrClosed What to set the module status to. Can only be "open" or "closed"
* @return True if method executes without failure
*/
public static boolean setModuleCategoryStatusOpen(
String ApplicationRoot, String moduleCategory, String openOrClosed) {
log.debug("*** Setter.setModuleCategoryStatusOpen ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement prepstmt =
conn.prepareStatement("UPDATE modules SET moduleStatus = ? WHERE moduleCategory = ?")) {
prepstmt.setString(1, openOrClosed);
prepstmt.setString(2, moduleCategory);
prepstmt.execute();
log.debug("Set " + moduleCategory + " to " + openOrClosed);
result = true;
} catch (SQLException e) {
log.error("Could not open/close category: " + e.toString());
}
log.debug("*** END setModuleCategoryStatusOpen ***");
return result;
}
/**
* This method sets the module status to Closed. This information is absorbed by the Tournament
* Floor Plan
*
* @param ApplicationRoot Current running director of the application
* @param moduleId The identifier of the module that is been set to closed status
* @return Boolean result depicting success of statement
*/
public static boolean setModuleStatusClosed(String ApplicationRoot, String moduleId) {
log.debug("*** Setter.setModuleStatusClosed ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmt = conn.prepareCall("call moduleSetStatus(?, ?)")) {
log.debug("Preparing moduleSetStatus procedure");
callstmt.setString(1, moduleId);
callstmt.setString(2, "closed");
callstmt.execute();
log.debug("Executed moduleSetStatus");
result = true;
} catch (SQLException e) {
log.error("Could not execute moduleSetStatus: " + e.toString());
}
log.debug("*** END setModuleStatusClosed ***");
return result;
}
/**
* This method sets the module status to Open. This information is absorbed by the Tournament
* Floor Plan
*
* @param ApplicationRoot Current running director of the application
* @param moduleId The identifier of the module that is been set to open status
* @return Boolean result depicting success of statement
*/
public static boolean setModuleStatusOpen(String ApplicationRoot, String moduleId) {
log.debug("*** Setter.setModuleStatusOpen ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmt = conn.prepareCall("call moduleSetStatus(?, ?)")) {
log.debug("Preparing moduleSetStatus procedure");
callstmt.setString(1, moduleId);
callstmt.setString(2, "open");
callstmt.execute();
log.debug("Executed moduleSetStatus");
result = true;
} catch (SQLException e) {
log.error("Could not execute moduleSetStatus: " + e.toString());
}
log.debug("*** END setModuleStatusOpen ***");
return result;
}
/**
* Used by CSRF levels to store their CSRF attack string, that will be displayed in a CSRF forum
* for the class the user is in
*
* @param ApplicationRoot The current running context of the application
* @param message The String they want to store
* @param userId The identifier of the user in which to store the attack under
* @param moduleId The module identifier of which to store the message under
* @return A boolean value reflecting the success of the function
*/
public static boolean setStoredMessage(
String ApplicationRoot, String message, String userId, String moduleId) {
log.debug("*** Setter.setStoredMessage ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmt = conn.prepareCall("call resultMessageSet(?, ?, ?)")) {
log.debug("Preparing resultMessageSet procedure");
callstmt.setString(1, message);
callstmt.setString(2, userId);
callstmt.setString(3, moduleId);
callstmt.execute();
log.debug("Executed resultMessageSet");
result = true;
} catch (SQLException e) {
log.error("Could not execute resultMessageSet: " + e.toString());
}
log.debug("*** END setStoredMessage ***");
return result;
}
/**
* Sets user to suspended in the database for a specific amount of time. This prevents them from
* signing into the application
*
* @param ApplicationRoot Running context of application
* @param userId User Identifier of the to be suspended user
* @param numberOfMinutes Amount of minutes to suspend user
* @return Returns true if statement succeeds without fatal error
*/
public static boolean suspendUser(String ApplicationRoot, String userId, int numberOfMinutes) {
log.debug("*** Setter.suspendUser ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callstmnt = conn.prepareCall("CALL suspendUser(?, ?)")) {
log.debug("Prepairing suspendUser call");
callstmnt.setString(1, userId);
callstmnt.setInt(2, numberOfMinutes);
log.debug("Executing suspendUser statement on id '" + userId + "'");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.error("suspendUser Failure: " + e.toString());
result = false;
}
log.debug("*** END suspendUser ***");
return result;
}
/**
* Revokes a suspension that may have been applied to a user
*
* @param ApplicationRoot Running context of application
* @param userId The Identifier of the user that will be released from suspension
* @return
*/
public static boolean unSuspendUser(String ApplicationRoot, String userId) {
log.debug("*** Setter.unSuspendUser ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callstmnt = conn.prepareCall("CALL unSuspendUser(?)")) {
log.debug("Prepairing suspendUser call");
callstmnt.setString(1, userId);
log.debug("Executing unSuspendUser statement on id '" + userId + "'");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.error("unSuspendUser Failure: " + e.toString());
result = false;
}
log.debug("*** END unSuspendUser ***");
return result;
}
/**
* Used to increment a users CSRF counter for CSRF levels.
*
* @param ApplicationRoot The current running context of the application.
* @param moduleId The identifier of the module to increment the counter of
* @param userId The user to be incremented
* @return Boolean reflecting the success of the operation
*/
public static boolean updateCsrfCounter(String ApplicationRoot, String moduleId, String userId) {
log.debug("*** Setter.updateCsrfCounter ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmt = conn.prepareCall("call resultMessagePlus(?, ?)")) {
log.debug("Preparing resultMessagePlus procedure");
callstmt.setString(1, moduleId);
callstmt.setString(2, userId);
callstmt.execute();
result = true;
} catch (SQLException e) {
log.error("Could not execute resultMessagePlus: " + e.toString());
}
log.debug("*** END updateCsrfCounter ***");
return result;
}
/**
* @param ApplicationRoot The current running context of the application
* @param userName User name of the user
* @param currentPassword User's current password
* @param newPassword New password to use in update
* @return true if the password was updated, false if the current password did not verify
*/
public static boolean updatePassword(
String ApplicationRoot, String userName, String currentPassword, String newPassword) {
log.debug("*** Setter.updatePassword ***");
boolean result = false;
// Phase 1: Verify current password (Getter.authUser manages its own connection)
log.debug("Checking current password");
String user[] = Getter.authUser(ApplicationRoot, userName, currentPassword);
if (user == null || user[0].isEmpty()) {
// Wrong password
log.debug("Current password incorrect!");
return false;
}
// Phase 2: Hash new password (CPU-bound, ~100-200ms — do not hold a DB connection)
log.debug("Hashing password");
Argon2 argon2 = Argon2Factory.create();
String newHash = argon2.hash(10, 65536, 1, newPassword.toCharArray());
// TODO: wipe password from memory after hashing
// Phase 3: Persist the new hash (short DB hold)
log.debug("Preparing userPasswordChange call");
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmnt = conn.prepareCall("call userPasswordChange(?, ?)")) {
callstmnt.setString(1, userName);
callstmnt.setString(2, newHash);
log.debug("Executing userPasswordChange");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.debug("Could not update password: " + e.toString());
throw new RuntimeException(e);
}
log.debug("*** END updatePassword ***");
return result;
}
/**
* @param ApplicationRoot The current running context of the application
* @param userName Existing user name to rename
* @param newUsername The new user name to assign
* @return true if the username was updated successfully
*/
public static boolean updateUsername(
String ApplicationRoot, String userName, String newUsername) {
log.debug("*** Setter.updateUsername ***");
boolean result = false;
log.debug("Preparing username change call from username " + userName + " to " + newUsername);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement prestmnt =
conn.prepareStatement(
"UPDATE users SET userName = ?, tempUsername = FALSE WHERE userName = ?;")) {
prestmnt.setString(1, newUsername);
prestmnt.setString(2, userName);
log.debug("Executing name change query");
prestmnt.execute();
result = true;
} catch (SQLException e) {
log.debug("Could not update username: " + e.toString());
throw new RuntimeException(e);
}
log.debug("*** END updateUsername ***");
return result;
}
/**
* Updates a player's password without needing the current password
*
* @param ApplicationRoot Running context of the applicaiton
* @param userId The user id of the user to update
* @param newPassword The new password to assign to the user
* @return
*/
public static boolean updatePasswordAdmin(
String ApplicationRoot, String userId, String newPassword) {
log.debug("*** Setter.updatePasswordAdmin ***");
boolean result = false;
// Hash before opening a DB connection (Argon2 is CPU-bound, ~100-200ms)
log.debug("Hashing password");
Argon2 argon2 = Argon2Factory.create();
String newHash = argon2.hash(10, 65536, 1, newPassword.toCharArray());
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmnt = conn.prepareCall("call userPasswordChangeAdmin(?, ?)")) {
log.debug("Preparing userPasswordChangeAdmin call");
callstmnt.setString(1, userId);
callstmnt.setString(2, newHash);
log.debug("Executing userPasswordChangeAdmin");
callstmnt.execute();
result = true;
} catch (SQLException e) {
log.error("updatePasswordAdmin Failure: " + e.toString());
}
log.debug("*** END updatePasswordAdmin ***");
return result;
}
/**
* Updates a PLAYER's class identifier
*
* @param ApplicationRoot The current running context of the application
* @param classId New class to be assigned to
* @param playerId Player to be assigned to new class
* @return The userName that was updated
*/
public static String updatePlayerClass(String ApplicationRoot, String classId, String playerId) {
log.debug("*** Setter.updatePlayerClass ***");
String result = null;
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Preparing playerUpdateClass call");
try (CallableStatement callstmnt = conn.prepareCall("call playerUpdateClass(?, ?)")) {
callstmnt.setString(1, playerId);
callstmnt.setString(2, classId);
log.debug("Executing playerUpdateClass");
try (ResultSet resultSet = callstmnt.executeQuery()) {
resultSet.next();
result = resultSet.getString(1);
}
}
} catch (SQLException e) {
log.error("playerUpdateClass Failure: " + e.toString());
result = null;
}
log.debug("*** END updatePlayerClass ***");
return result;
}
/**
* Updates a PLAYER's class identifier to null
*
* @param ApplicationRoot The current running context of the application
* @param playerId The identifier of the player to be assigned to class NULL
* @return The userName that was updated
*/
public static String updatePlayerClassToNull(String ApplicationRoot, String playerId) {
log.debug("*** Setter.updatePlayerClassToNull ***");
String result = null;
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Preparing playerUpdateClassToNull call");
try (CallableStatement callstmnt = conn.prepareCall("call playerUpdateClassToNull(?)")) {
callstmnt.setString(1, playerId);
log.debug("Executing playerUpdateClassToNull");
try (ResultSet resultSet = callstmnt.executeQuery()) {
resultSet.next();
result = resultSet.getString(1);
}
}
} catch (SQLException e) {
log.error("updatePlayerClassToNull Failure: " + e.toString());
result = null;
}
log.debug("*** END updatePlayerClassToNull ***");
return result;
}
/**
* Updates a users result of a specific module
*
* @param ApplicationRoot The current running context of the application
* @param moduleId Identifier of the module the user is completing
* @param userId Identifier of the user completing the module
* @param extra The additional comments submitted in feedback by the user, or if CSRF, the attack
* string they used
* @param before The knowledge the user felt before they completed the level
* @param after The knowledge the user felt after they completed the level
* @param difficulty The difficulty the user felt they encountered
* @return The module name of the module completed by the user
*/
public static String updatePlayerResult(
String ApplicationRoot,
String moduleId,
String userId,
String extra,
int before,
int after,
int difficulty) {
log.debug("*** Setter.updatePlayerResult ***");
String result = null;
boolean isOpen;
Boolean isRunning;
try {
isOpen = CountdownHandler.isOpen();
isRunning = CountdownHandler.isRunning();
} catch (InvalidCountdownStateException e1) {
String message = "Countdown handler is in an invalid state.";
log.error(message);
throw new RuntimeException(message);
}
if (isRunning) {
boolean updated = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmnt =
conn.prepareCall("call userUpdateResult(?, ?, ?, ?, ?, ?, ?)")) {
log.debug("Preparing userUpdateResult call");
callstmnt.setString(1, moduleId);
callstmnt.setString(2, userId);
callstmnt.setInt(3, before);
callstmnt.setInt(4, after);
callstmnt.setInt(5, difficulty);
callstmnt.setBoolean(6, isOpen); // Only give points if CTF is open
callstmnt.setString(7, extra);
log.debug("Executing userUpdateResult");
callstmnt.execute();
updated = true;
} catch (SQLException e) {
log.error("userUpdateResult Failure: " + e.toString());
}
// Release the core connection before calling back into Getter, which opens its own.
// Holding two pooled connections per call doubles pool pressure under concurrency.
if (updated) {
result = Getter.getModuleNameLocaleKey(ApplicationRoot, moduleId);
}
} else {
log.error("Error: Can't allow results to be stored when CTF isn't running");
result = null;
}
log.debug("*** END updatePlayerResult ***");
return result;
}
/**
* Adds or Subtracts points from a user.
*
* @param ApplicationRoot Running context of application
* @param userId Identifier of user to update
* @param points Positive or Negative number of points to update by
* @return Returns true if statement executes without fatal error
*/
public static boolean updateUserPoints(String ApplicationRoot, String userId, int points) {
log.debug("*** Setter.updateUserPoints ***");
boolean result = false;
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement prestmnt =
conn.prepareStatement("UPDATE users SET userScore = userScore + ? WHERE userId = ?")) {
log.debug("Preparing updateUserPoints call");
prestmnt.setInt(1, points);
prestmnt.setString(2, userId);
log.debug("Executing updateUserPoints");
prestmnt.execute();
result = true;
} catch (SQLException e) {
log.error("updateUserPoints Failure: " + e.toString());
}
log.debug("*** END updateUserPoints ***");
return result;
}
/**
* Updates a USER's role
*
* @param ApplicationRoot The current running context of the application
* @param playerId The identifier of the player to update
* @param newRole Must be "player" or "admin"
* @return The user name of the user updated
*/
public static String updateUserRole(String ApplicationRoot, String playerId, String newRole) {
log.debug("*** Setter.updateUserRole ***");
String result = null;
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Preparing userUpdateRole call");
try (CallableStatement callstmnt = conn.prepareCall("call userUpdateRole(?, ?)")) {
callstmnt.setString(1, playerId);
callstmnt.setString(2, newRole);
log.debug("Executing userUpdateRole");
try (ResultSet resultSet = callstmnt.executeQuery()) {
resultSet.next();
result = resultSet.getString(1);
}
}
} catch (SQLException e) {
log.error("userUpdateRole Failure: " + e.toString());
result = null;
}
log.debug("*** END updateUserRole ***");
return result;
}
/**
* Used by many functions to create players or admins
*
* @param ApplicationRoot
* @param classId Cannot be null, relationship depending
* @param userName Cannot be null
* @param userPass Cannot be null
* @param userRole Cannot be null, must be "player" or "admin"
* @param userAddress Must be unique
* @param tempPass Whether or not to set the user with a temporary pass flag
* @return A boolean value determining the result of the creation
* @throws SQLException If the creation fails, a Exception is thrown
*/
public static boolean userCreate(
String ApplicationRoot,
String classId,
String userName,
String userPass,
String userRole,
String userAddress,
boolean tempPass)
throws SQLException {
boolean result = false;
log.debug("*** Setter.userCreate ***");
log.debug("classId = " + classId);
log.debug("userName = " + userName);
// We don't log passwords
log.debug("userRole = " + userRole);
log.debug("userAddress = " + userAddress);
// Hash before opening a DB connection (Argon2 is CPU-bound, ~100-200ms)
log.debug("Hashing password");
Argon2 argon2 = Argon2Factory.create();
String hash = argon2.hash(10, 65536, 1, userPass.toCharArray());
// TODO: wipe password from memory after hashing
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
CallableStatement callstmt =
conn.prepareCall("call userCreate(?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
log.debug("Executing userCreate procedure on Database");
callstmt.setString(1, classId);
callstmt.setString(2, userName);
callstmt.setString(3, hash);
callstmt.setString(4, userRole);
callstmt.setString(5, null); // ssoName
callstmt.setString(6, userAddress);
callstmt.setString(7, "login"); // login type
callstmt.setBoolean(8, tempPass);
callstmt.setBoolean(9, false); // Tempname
try (ResultSet registerAttempt = callstmt.executeQuery()) {
log.debug("Opening result set");
registerAttempt.next(); // Procedure Ran correctly
if (registerAttempt.getString(1) == null) {
// Registration success
log.debug("Register Success");
result = true;
} else {
// Registration failure
result = false;
log.debug("ResultSet contained -> " + registerAttempt.getString(1));
throw new SQLException(registerAttempt.getString(1));
}
}
}
log.debug("*** END userCreate ***");
return result;
}
public static String userCreateSSO(
String ApplicationRoot, String classId, String userName, String ssoName, String userRole)
throws SQLException {
String result = null;
log.debug("*** Setter.userCreateSSO ***");
log.debug("classId = " + classId);
log.debug("userName = " + userName);
log.debug("ssoName = " + ssoName);
// We don't log passwords
String newUsername = userName;
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
try {
log.debug("Checking for duplicate usernames");
boolean isDuplicate = true;
int duplicateCounter = 0;
while (isDuplicate) {
try (PreparedStatement prestmt =
conn.prepareStatement("SELECT ssoName FROM `users` WHERE userName = ?")) {
prestmt.setString(1, newUsername);
try (ResultSet checkDuplicate = prestmt.executeQuery()) {
log.debug("Opening result set");
if (checkDuplicate.next()) {
// Found a duplicate user, sigh
isDuplicate = true;
duplicateCounter++;
newUsername = userName + String.valueOf(duplicateCounter);
log.debug(
"Duplicate username found, changing to "
+ newUsername
+ " counter "
+ String.valueOf(duplicateCounter));
} else {
isDuplicate = false;
}
}
}
if (duplicateCounter > 500) {
String message =
"Bailing out of the de-duplicate loop at " + String.valueOf(duplicateCounter);
log.error(message);
throw new RuntimeException(message);
}
}
} catch (SQLException e) {
log.fatal("Failed to check for duplicate usernames: " + e.toString());
throw new SQLException(e);
}
log.debug("Executing userCreate procedure on Database");
try (CallableStatement callstmt =
conn.prepareCall("call userCreate(?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
callstmt.setString(1, classId);
callstmt.setString(2, newUsername);
callstmt.setString(3, "DISABLED");
callstmt.setString(4, userRole);
callstmt.setString(5, ssoName);
callstmt.setString(6, ""); // userAddress
callstmt.setString(7, "saml"); // login type
callstmt.setBoolean(8, false); // temppass
callstmt.setBoolean(9, true); // Tempname
try (ResultSet registerAttempt = callstmt.executeQuery()) {
log.debug("Opening result set");
registerAttempt.next(); // Procedure Ran correctly
if (registerAttempt.getString(1) == null) {
// Registration success
log.debug("Register Success");
result = newUsername;
} else {
// Registration failure
result = null;
log.debug("ResultSet contained -> " + registerAttempt.getString(1));
throw new SQLException(registerAttempt.getString(1));
}
}
} catch (SQLException e) {
log.fatal("userCreate Failure: " + e.toString());
throw new SQLException(e);
}
}
log.debug("*** END userCreateSSO ***");
return result;
}
public static boolean userDelete(String ApplicationRoot, String userId) throws SQLException {
boolean result = false;
log.debug("*** Setter.userDelete ***");
log.debug("userId = " + userId);
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Deleting User's Results");
try (PreparedStatement callDelResults =
conn.prepareStatement("DELETE FROM results WHERE userId = ?")) {
callDelResults.setString(1, userId);
callDelResults.executeUpdate();
}
log.debug("Executing delete from users on Database");
try (PreparedStatement callUserDel =
conn.prepareStatement("DELETE FROM users WHERE userId = ?")) {
callUserDel.setString(1, userId);
int deleteAttemptResult = callUserDel.executeUpdate();
if (deleteAttemptResult == 1) {
result = true;
}
}
} catch (SQLException sqlEx) {
log.fatal("userDelete Failure: " + sqlEx.toString());
throw new SQLException(sqlEx);
}
log.debug("*** END userDelete ***");
return result;
}
public static boolean setAdminCheatStatus(String ApplicationRoot, boolean adminCheatsEnabled)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setAdminCheatStatus ***");
log.debug("adminCheatsEnabled = " + adminCheatsEnabled);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callAdminSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting admin cheat setting");
callAdminSetting.setBoolean(1, adminCheatsEnabled);
callAdminSetting.setString(2, "adminCheatsEnabled");
if (callAdminSetting.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set admin cheat setting");
}
}
log.debug("*** END setAdminCheatStatus ***");
return result;
}
public static boolean setPlayerCheatStatus(String ApplicationRoot, boolean playerCheatsEnabled)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setPlayerCheatStatus ***");
log.debug("playerCheatsEnabled = " + playerCheatsEnabled);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement callPlayerSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting player cheat setting");
callPlayerSetting.setBoolean(1, playerCheatsEnabled);
callPlayerSetting.setString(2, "playerCheatsEnabled");
if (callPlayerSetting.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set player cheat setting");
}
}
log.debug("*** END setPlayerCheatStatus ***");
return result;
}
public static boolean setModuleLayout(String ApplicationRoot, String theModuleLayout)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setModulelayout ***");
log.debug("moduleLayout = " + theModuleLayout);
if (!"ctf".equals(theModuleLayout)
&& !"tournament".equals(theModuleLayout)
&& !"open".equals(theModuleLayout)) {
throw new IllegalArgumentException("Invalid module layout: " + theModuleLayout);
}
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement moduleLayoutSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting module layout");
moduleLayoutSetting.setString(1, theModuleLayout);
moduleLayoutSetting.setString(2, "modulelayout");
if (moduleLayoutSetting.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set module layout to " + theModuleLayout);
}
}
log.debug("*** END setModulelayout ***");
return result;
}
public static boolean setFeedbackStatus(String ApplicationRoot, boolean theFeebackStatus)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setFeedbackStatus ***");
log.debug("feedbackStatus = " + theFeebackStatus);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement getFeedbackSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting feedback status setting");
getFeedbackSetting.setBoolean(1, theFeebackStatus);
getFeedbackSetting.setString(2, "enableFeedback");
int updateResult = getFeedbackSetting.executeUpdate();
if (updateResult == 1) {
result = true;
} else {
throw new RuntimeException("Could not set feedback status to " + theFeebackStatus);
}
}
log.debug("*** END setFeedbackStatus ***");
return result;
}
public static boolean setRegistrationStatus(String ApplicationRoot, boolean theRegistrationStatus)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setRegistrationStatus ***");
log.debug("enableRegistration = " + theRegistrationStatus);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement setRegistrationSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting registration status setting");
setRegistrationSetting.setBoolean(1, theRegistrationStatus);
setRegistrationSetting.setString(2, "openRegistration");
if (setRegistrationSetting.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set registration status to " + theRegistrationStatus);
}
}
log.debug("*** END setRegistrationStatus ***");
return result;
}
public static boolean setScoreboardStatus(String ApplicationRoot, String theScoreboardStatus)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setScoreboardStatus ***");
log.debug("scoreboardStatus = " + theScoreboardStatus);
if (!"closed".equals(theScoreboardStatus)
&& !"adminOnly".equals(theScoreboardStatus)
&& !"classSpecific".equals(theScoreboardStatus)
&& !"open".equals(theScoreboardStatus)
&& !"public".equals(theScoreboardStatus)) {
throw new IllegalArgumentException("Invalid scoreboard status: " + theScoreboardStatus);
}
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement scoreboardSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting scoreboard status setting");
scoreboardSetting.setString(1, theScoreboardStatus);
scoreboardSetting.setString(2, "scoreboardStatus");
if (scoreboardSetting.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set scoreboard status to " + theScoreboardStatus);
}
}
log.debug("*** END setScoreboardStatus ***");
return result;
}
public static boolean setScoreboardClass(String ApplicationRoot, String theScoreboardClass)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setScoreboardClass ***");
log.debug("scoreboardClass = " + theScoreboardClass);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement scoreboardClassSetting =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting scoreboard class setting");
scoreboardClassSetting.setString(1, theScoreboardClass);
scoreboardClassSetting.setString(2, "scoreboardClass");
if (scoreboardClassSetting.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set scoreboard class to " + theScoreboardClass);
}
}
log.debug("*** END setScoreboardClass ***");
return result;
}
public static boolean setStartTimeStatus(String ApplicationRoot, boolean theStartTimeStatus)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setStartTimeStatus ***");
log.debug("theLockTimeStatus = " + theStartTimeStatus);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement lockTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting start time setting");
lockTimeStatement.setBoolean(1, theStartTimeStatus);
lockTimeStatement.setString(2, "hasStartTime");
if (lockTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set start time status to " + theStartTimeStatus);
}
}
log.debug("*** END setStartTimeStatus ***");
return result;
}
public static boolean setStartTime(String ApplicationRoot, LocalDateTime theStartTime)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setStartTime ***");
log.debug("theLockTime = " + theStartTime);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement lockTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting start time");
lockTimeStatement.setString(1, theStartTime.toString());
lockTimeStatement.setString(2, "startTime");
if (lockTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set start time to " + theStartTime);
}
}
log.debug("*** END setStartTime ***");
return result;
}
public static boolean setLockTimeStatus(String ApplicationRoot, boolean theLockTimeStatus)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setLockTimeStatus ***");
log.debug("theLockTimeStatus = " + theLockTimeStatus);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement lockTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting lock timestamp setting");
lockTimeStatement.setBoolean(1, theLockTimeStatus);
lockTimeStatement.setString(2, "hasLockTime");
if (lockTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set lock time status to " + theLockTimeStatus);
}
}
log.debug("*** END setLockTimeStatus ***");
return result;
}
public static boolean setLockTime(String ApplicationRoot, LocalDateTime theLockTime)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setLockTime ***");
log.debug("theLockTime = " + theLockTime);
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Setting lock time");
try (PreparedStatement lockTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
lockTimeStatement.setString(1, theLockTime.toString());
lockTimeStatement.setString(2, "lockTime");
if (lockTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set lock time to " + theLockTime);
}
}
}
log.debug("*** END setLockTime ***");
return result;
}
public static boolean setEndTimeStatus(String ApplicationRoot, boolean theEndTimeStatus)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setEndTimeStatus ***");
log.debug("theEndTimeStatus = " + theEndTimeStatus);
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Setting end time setting");
try (PreparedStatement lockTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
lockTimeStatement.setBoolean(1, theEndTimeStatus);
lockTimeStatement.setString(2, "hasEndTime");
if (lockTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set end time status to " + theEndTimeStatus);
}
}
}
log.debug("*** END theEndTimeStatus ***");
return result;
}
public static boolean setEndTime(String ApplicationRoot, LocalDateTime theEndTime)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setEndTime ***");
log.debug("theLockTime = " + theEndTime);
try (Connection conn = Database.getCoreConnection(ApplicationRoot)) {
log.debug("Setting end time");
try (PreparedStatement endTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
endTimeStatement.setString(1, theEndTime.toString());
endTimeStatement.setString(2, "endTime");
if (endTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set end time to " + theEndTime);
}
}
}
log.debug("*** END setEndTime ***");
return result;
}
public static boolean setDefaultClass(String ApplicationRoot, String theDefaultClass)
throws SQLException {
boolean result = false;
log.debug("*** Setter.setDefaultClass ***");
log.debug("theLockTime = " + theDefaultClass);
try (Connection conn = Database.getCoreConnection(ApplicationRoot);
PreparedStatement endTimeStatement =
conn.prepareStatement("UPDATE settings SET value = ? WHERE setting = ?")) {
log.debug("Setting default class");
endTimeStatement.setString(1, theDefaultClass);
endTimeStatement.setString(2, "defaultClass");
if (endTimeStatement.executeUpdate() == 1) {
result = true;
} else {
throw new RuntimeException("Could not set default class to " + theDefaultClass);
}
}
log.debug("*** END setDefaultClass ***");
return result;
}
}