package dbProcs;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Used to create database connections using connection pooling via HikariCP. Connections are
* obtained from the pool and returned when closed. Initiated by Getter.java, Setter.java
*
* 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
* @author Paul (connection pooling)
*/
public class Database {
private static final Logger log = LogManager.getLogger(Database.class);
/**
* This method is used by the application to get a connection for challenge schemas. Connections
* are obtained from a pool specific to the challenge.
*
* @param driverType The JDBC driver type (kept for API compatibility, but not used with pooling)
* @param connectionURL The base connection URL
* @param dbOptions Database connection options
* @param dbUsername Database username
* @param dbPassword Database password
* @return A pooled connection
* @throws SQLException if a connection cannot be obtained
*/
public static Connection getConnection(
String driverType,
String connectionURL,
String dbOptions,
String dbUsername,
String dbPassword)
throws SQLException {
// Extract the schema portion from the connectionURL for challenge connections
// The connectionURL at this point already includes the schema
return ConnectionPool.getChallengeConnection(
"", // Base URL is already included in connectionURL
connectionURL,
dbOptions,
dbUsername,
dbPassword);
}
/**
* This method is used by the application to close/return a connection to the pool. With
* connection pooling, this returns the connection to the pool for reuse.
*
* @param conn The connection to return to the pool
*/
public static void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close(); // With HikariCP, this returns the connection to the pool
} catch (SQLException e) {
log.warn("Error returning connection to pool: " + e.getMessage());
}
}
}
/**
* This method is used by the application to get a connection to the secure database sever based
* on the input path to a specific properties file.
*
* @param ApplicationRoot The running context of the application.
* @param path The path to the properties file to use for this connection. this is filtered for
* path traversal attacks
* @return A connection to the secure database server
* @throws IOException
* @throws FileNotFoundException
* @throws SQLException
*/
public static Connection getChallengeConnection(String ApplicationRoot, String path)
throws SQLException {
// Some over paranoid input validation never hurts.
path = path.replaceAll("\\.", "").replaceAll("/", "");
log.debug("Path = " + path);
Connection conn = null;
Properties prop = new Properties();
// Pull Driver and DB URL out of database.properties
String mysql_props = Constants.MYSQL_DB_PROP;
try (InputStream mysql_input = new FileInputStream(mysql_props)) {
prop.load(mysql_input);
} catch (IOException e) {
log.error("Could not load properties file: " + e.toString());
throw new RuntimeException(e);
}
String errorBase = "Missing property :";
String connectionURL = prop.getProperty("databaseConnectionURL");
if (connectionURL == null) {
String message = errorBase + "connectionURL";
log.fatal(message);
throw new RuntimeException(message);
}
if (connectionURL.contains("?")) {
// Check for old-style challenge properties files that put options in connection url
String message = "Connection URL cannot contain options!";
log.fatal(message);
throw new RuntimeException(message);
}
String driverType = prop.getProperty("DriverType");
if (driverType == null) {
String message = errorBase + "DriverType";
log.fatal(message);
throw new RuntimeException(message);
}
// Pull DB Schema, Schema User name and Schema Password from level specific
// properties File
String challenge_props =
new File(Database.class.getResource("/challenges/" + path + ".properties").getFile())
.getAbsolutePath();
log.debug("Level Properties File = " + path + ".properties");
try (InputStream mysql_input = new FileInputStream(challenge_props)) {
prop.load(mysql_input);
} catch (IOException e) {
log.error("Could not load properties file: " + e.toString());
throw new RuntimeException(e);
}
String challenge_connectionURL = prop.getProperty("databaseConnectionURL");
if (challenge_connectionURL == null) {
throw new RuntimeException(errorBase + "connectionURL");
}
String dbOptions = prop.getProperty("databaseOptions");
if (dbOptions == null) {
log.debug(
"Did not find database options, defaulting to"
+ " useUnicode=true&character_set_server=utf8mb4");
dbOptions = "useUnicode=true&character_set_server=utf8mb4";
}
String username = prop.getProperty("databaseUsername");
if (username == null) {
throw new RuntimeException(errorBase + "databaseUsername");
}
String password = prop.getProperty("databasePassword");
if (password == null) {
throw new RuntimeException(errorBase + "databasePassword");
}
connectionURL += challenge_connectionURL;
conn = getConnection(driverType, connectionURL, dbOptions, username, password);
return conn;
}
public static Connection getCoreConnection() throws SQLException, IOException {
return getCoreConnection("");
}
/**
* Gets a connection to the core database schema from the connection pool.
*
* @param ApplicationRoot The running context of the application (kept for API compatibility)
* @return Connection to core schema from the pool
* @throws SQLException if a connection cannot be obtained
*/
public static Connection getCoreConnection(String ApplicationRoot) throws SQLException {
// Use the connection pool for core connections
return ConnectionPool.getConnection();
}
public static Connection getDatabaseConnection(String ApplicationRoot)
throws SQLException, IOException {
return getDatabaseConnection(ApplicationRoot, false);
}
/**
* This method is used by the application to get a connection to the secure database sever
*
* @param ApplicationRoot The running context of the application.
* @return A connection to the secure database server
* @throws SQLException
* @throws IOException
* @throws RuntimeException
*/
public static Connection getDatabaseConnection(String ApplicationRoot, boolean allowMulti)
throws SQLException, IOException {
Connection conn = null;
Properties prop = new Properties();
// Pull Driver and DB URL out of database.properties
String mysql_props = Constants.MYSQL_DB_PROP;
try (InputStream mysql_input = new FileInputStream(mysql_props)) {
prop.load(mysql_input);
}
String errorBase = "Missing property :";
String connectionURL = prop.getProperty("databaseConnectionURL");
if (connectionURL == null) {
throw new RuntimeException(errorBase + "connectionURL");
}
String dbOptions = prop.getProperty("databaseOptions");
if (dbOptions == null) {
throw new RuntimeException(errorBase + "databaseOptions");
}
String driverType = prop.getProperty("DriverType");
if (driverType == null) {
throw new RuntimeException(errorBase + "DriverType");
}
String username = prop.getProperty("databaseUsername");
if (username == null) {
throw new RuntimeException(errorBase + "databaseUsername");
}
String password = prop.getProperty("databasePassword");
if (password == null) {
throw new RuntimeException(errorBase + "databasePassword");
}
if (allowMulti) {
if (dbOptions.length() > 0) {
dbOptions += "&";
}
dbOptions += "allowMultiQueries=true";
}
conn = getConnection(driverType, connectionURL, dbOptions, username, password);
return conn;
}
/**
* This method is used by the application to get a connection to the secure database sever's SQL
* injection Lesson schema
*
* @param ApplicationRoot The running context of the application.
* @return A connection to the secure database server
* @throws SQLException
* @throws IOException
*/
public static Connection getSqlInjLessonConnection(String ApplicationRoot)
throws SQLException, IOException {
Connection conn = null;
Properties prop = new Properties();
String mysql_props = Constants.MYSQL_DB_PROP;
try (InputStream mysql_input = new FileInputStream(mysql_props)) {
prop.load(mysql_input);
}
String errorBase = "Missing property :";
String connectionURL = prop.getProperty("databaseConnectionURL");
if (connectionURL == null) {
throw new RuntimeException(errorBase + "connectionURL");
}
String dbOptions = prop.getProperty("databaseOptions");
if (dbOptions == null) {
throw new RuntimeException(errorBase + "databaseOptions");
}
String driverType = prop.getProperty("DriverType");
if (driverType == null) {
throw new RuntimeException(errorBase + "DriverType");
}
// Pull Schema, User name and Password from SqlInjLesson.properties
String sql_inj_props = ApplicationRoot + "/WEB-INF/classes/lessons/SqlInjLesson.properties";
try (InputStream sql_inj_input = new FileInputStream(sql_inj_props)) {
prop.load(sql_inj_input);
}
String injection_URL = prop.getProperty("databaseConnectionURL");
if (injection_URL == null) {
throw new RuntimeException(errorBase + "SQL injection connectionURL");
}
String username = prop.getProperty("databaseUsername");
if (username == null) {
throw new RuntimeException(errorBase + "SQL injection databaseUsername");
}
String password = prop.getProperty("databasePassword");
if (password == null) {
throw new RuntimeException(errorBase + "SQL injection databasePassword");
}
conn = getConnection(driverType, connectionURL + injection_URL, dbOptions, username, password);
return conn;
}
}