Moving on to the next steps! Previous review was here. The idea here was to implement suggestions made by @hd/@Pimgd and then implement an effective way of tracking and logging results when an exception is thrown. With that said, my focus is still on security of my application, but now I also really want feedback on the data being sent when an error is triggered, and it's effectiveness or usefulness.
I ended up creating 2 custom exceptions for better naming of what error is happening - PDOEmptyResultException and PDOQueryException. If the latter is thrown, then something went wrong. If the former is thrown, then it's not so much an all hands on deck problem as a research and figure out what went wrong situation.
DBManager
/**
* DBManager constructor.
*/
public function __construct() {
try {
$this->db = new PDO('mysql:dbname='.getenv('DB_DATABASE').';host='.getenv('DB_HOST'),getenv('DB_USERNAME'), getenv('DB_PASSWORD'));
} catch(\PDOException $pdoe) {
$trace = $pdoe->getTrace();
$ip = $_SERVER['REMOTE_ADDR'];
$message = $pdoe->getMessage();
$headers = array('trace'=>$trace,'ip'=>$ip,'message'=>$message);
Mail::send(['html' => 'emails.errors.pdoconnectexception'],$headers, function($m) {
$m->from('[email protected]');
$m->to('[email protected]')->subject('PDOQueryException WebbugRedirect');
});
}
date_default_timezone_set('America/New_York');
}
/**
* query
* Public facing method for executing queries. It will return the result set back.
*
* @param string $sql The query to be prepared and executed
* @param array $bindings An array of query parameters
* @return array Array of results from query
* @throws PDOQueryException
*/
public function query($sql,$bindings) {
$result = $this->queryErrors($sql,$bindings);
return $result;
}
/**
* queryErrors
* Calls the function to create and execute the prepared statement.
*
* @param string $sql The query to be prepared and executed
* @param array $bindings An array of query parameters
* @return array Array of results from the prepared statement
* @throws PDOQueryException Checks if result set is empty or null
*/
private function queryErrors($sql,$bindings) {
$result = $this->createPreparedStatement($sql,$bindings);
if(is_null($result)) {
$message = "Prepared statement returned null.\nError Code: " .
$this->db->errorCode() . "\nError Info: " . array_values($this->db->errorInfo());
throw new PDOQueryException($message,$sql);
} else if(empty($result)) {
$message = "Prepared statement returned no results.";
throw new PDOEmptyResultExecption($message);
}
return $result;
}
/**
* bindParams
* Creates an array of the binding parameters for a prepared statement.
*
* @param array $bindings An array of query parameters
* @return array Array of results from the prepared statement
*/
private function bindParams($bindings) {
$params = array();
for($i = 0; $i < count($bindings); $i++) {
$params[] = & $bindings[$i];
}
return $params;
}
/**
* createPreparedStatement
* Prepares the query($sql), binds the parameters, executes the query, then returns the result set.
*
* @param string $sql The query to be prepared and executed
* @param array $bindings An array of query parameters
* @return array Array of results from the prepared statement
* @throws PDOQueryException Checks if prepared statement was successful created and executed
*/
private function createPreparedStatement($sql,$bindings) {
$stmt = $this->db->prepare($sql);
if($stmt === false) {
$message = "Failed to generate prepared statement.\nError Code: " .
$this->db->errorCode() . "\nError Info: " . array_values($this->db->errorInfo());
throw new PDOQueryException($message,$sql);
}
$params = $this->bindParams($bindings);
$result = $stmt->execute($params);
if($result === false) {
$message = "Failed to execute prepared statement.\nError Code: " .
$this->db->errorCode() . "\nError Info: " . array_values($this->db->errorInfo());
throw new PDOQueryException($message,$sql);
}
return $stmt->fetchAll();
}
In theory, the PhishingController should only be executing and should have as little logic in it as possible. If there are any suggestions that could be made to extrapolate classes out of these methods below, that would be appreciated.
PhishingController
/**
* webbugRedirect
* Handles when webbugs get called. If request URI contains the word 'email', executes email webbug otherwise executes website webbug
*
* @param string $id Contains UniqueURLId that references specific user and specific project ID
*/
public function webbugRedirect($id) {
$urlId = substr($id,0,15);
$projectId = substr($id,15,16);
$db = new DBManager();
$sql = "SELECT USR_Username FROM gaig_users.users WHERE USR_UniqueURLId=?;";
$bindings = array($urlId);
try {
$result = $db->query($sql,$bindings);
$username = $result[0]['USR_Username'];
if(strpos($_SERVER['REQUEST_URI'],'email') !== false) {
$this->webbugExecutionEmail($username);
} else {
$this->webbugExecutionWebsite($username);
}
} catch(PDOQueryException $pdoe) {
$trace = $pdoe->getTrace();
$errorcode = $db->getErrorCode();
$errorinfo = $db->getErrorInfo();
$ip = $_SERVER['REMOTE_ADDR'];
$message = $pdoe->getMessage();
$sql = $pdoe->getQuery();
$params = array($urlId,$projectId);
$headers = array('trace'=>$trace,'errorcode'=>$errorcode,'erorrinfo'=>$errorinfo,'ip'=>$ip,'message'=>$message,'sql'=>$sql,'params'=>$params);
Mail::send(['html' => 'emails.errors.pdoqueryexception'],$headers, function($m) {
$m->from('');
$m->to('')->subject('PDOQueryException WebbugRedirect');
});
} catch(PDOEmptyResultExecption $pdoe) {
/*
* 1) user was deleted
* 2) user failed to be created
* 3) uniqueURLId failed to insert to database on first email sent
*/
}
/**
* webbugRootExecution
* Common values for webbug execution. Returns array of values to calling method.
*
* @param int $strLocation Index of UniqueURLId in REQUEST_URI
* @return array|null Returns null if IP is hidden or not given, otherwise gives needed input
*/
private function webbugRootExecution($strLocation) {
if(!empty($_SERVER['REMOTE_ADDR'])) {
return null;
}
$db = new DBManager();
$ip = $_SERVER['REMOTE_ADDR'];
$host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
$reqpath = $_SERVER['REQUEST_URI'];
$projectID = substr($reqpath, $strLocation);
$projectID = ltrim($projectID, '0');
$projectID = rtrim($projectID, '.');
$projectID = intval(strval($projectID));
$sql = "SELECT PRJ_ProjectName FROM gaig_users.projects WHERE PRJ_ProjectId=?;";
$bindings = array($projectID);
$result = $db->query($sql,$bindings);
$projectName = $result[0]['PRJ_ProjectName'];
$date = date("Y-m-d");
$time = date("H:i:s");
return array($ip,$host,$projectName,$date,$time);
}
/**
* webbugExecutionEmail
* Email specific execution of the webbug tracker.
*
* @param string $username Username of user passed from webbugRedirect
*/
private function webbugExecutionEmail($username) {
$db = new DBManager();
$data = $this->webbugRootExecution(29);
if(!is_null($data)) {
$sql = "INSERT INTO gaig_users.email_tracking (EML_Id,EML_Ip,EML_Host,EML_Username,EML_ProjectName,
EML_AccessDate,EML_AccessTime) VALUES (null,?,?,?,?,?,?);";
$bindings = array($data[0],$data[1],$username,$data[2],$data[3],$data[4]);
$db->query($sql,$bindings);
}
}
As always, thanks for the feedback!