Here comes the next round. I've implemented some of the suggestions from the previous review. That being said, though, there are a few things that I have on the horizons but haven't yet put in that were suggested. I am currently working on implementing dependency injection so that the database object is not redundantly instantiated. I am also working on implementing query iteration instead of returning the result set all at once.
As with previous reviews, I'm primarily looking to make sure that my implementation stays secure and efficient. I've added in a class that contains 2 static methods for generating logs on the server when something goes wrong. These functions generate the log and send an email. If the email fails, the log is still created.
DBManager
/**
* DBManager constructor.
*/
public function __construct() {
if($this->db == null) {
try {
$this->db = new PDO(getenv('DB_TYPE').':dbname='.getenv('DB_DATABASE').'1;host='.getenv('DB_HOST'),
getenv('DB_USERNAME'), getenv('DB_PASSWORD'));
} catch(\PDOException $pdoe) {
ErrorLogging::logConnectError(__CLASS__,__FUNCTION__,$pdoe->getMessage(),$pdoe->getTrace());
}
}
}
/**
* 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 \PDOException
*/
public function query($sql,$bindings) {
if(is_null($this->db)) {
throw new \PDOException();
}
$result = $this->executePreparedStatement($sql,$bindings);
return $result;
}
/**
* executePreparedStatement
* 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 QueryException Checks if prepared statement was successful created and executed
*/
private function executePreparedStatement($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 QueryException($message,$sql);
}
$result = $stmt->execute($bindings);
if($result === false) {
$message = "Failed to execute prepared statement.\nError Code: " .
$this->db->errorCode() . "\nError Info: " . array_values($this->db->errorInfo());
throw new QueryException($message,$sql);
}
//fetchAll() is used for now. Based off a previous suggestion, iteration will be implemented at a later date.
return $stmt->fetchAll();
}
public function getErrorCode() {
if(is_null($this->db)) {
throw new \PDOException();
}
return $this->db->errorCode();
}
public function getErrorInfo() {
if(is_null($this->db)) {
throw new \PDOException();
}
return $this->db->errorInfo();
}
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,$urlId,$projectId);
} else {
$this->webbugExecutionWebsite($username);
}
} catch(QueryException $pdoe) {
ErrorLogging::logQueryError(__CLASS__,__FUNCTION__,$pdoe,$db,array($urlId,$projectId));
} catch(\PDOException $pdoe) {
ErrorLogging::logConnectError(__CLASS__,__FUNCTION__,$pdoe->getMessage(),$pdoe->getTrace());
}
}
/**
* 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($urlId,$projectId) {
if(!empty($_SERVER['REMOTE_ADDR'])) {
$db = new DBManager();
$ip = $_SERVER['REMOTE_ADDR'];
$host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
$sql = "SELECT PRJ_ProjectName FROM gaig_users.projects WHERE PRJ_ProjectId=?;";
$bindings = array($projectId);
try {
$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);
} catch(QueryException $pdoe) {
ErrorLogging::logQueryError(__CLASS__,__FUNCTION__,$pdoe,$db,array($urlId,$projectId));
} catch(\PDOException $pdoe) {
ErrorLogging::logConnectError(__CLASS__,__FUNCTION__,$pdoe->getMessage(),$pdoe->getTrace());
}
}
return null;
}
/**
* webbugExecutionEmail
* Email specific execution of the webbug tracker.
*
* @param string $username Username of user passed from webbugRedirect
*/
private function webbugExecutionEmail($username,$urlId,$projectId) {
$db = new DBManager();
$data = $this->webbugRootExecution($urlId,$projectId);
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]);
try {
$db->query($sql,$bindings);
} catch(QueryException $pdoe) {
ErrorLogging::logQueryError(__CLASS__,__FUNCTION__,$pdoe,$db,array($urlId,$projectId));
} catch(\PDOException $pdoe) {
ErrorLogging::logConnectError(__CLASS__,__FUNCTION__,$pdoe->getMessage(),$pdoe->getTrace());
}
}
}
ErrorLogging - Mailing commented out for testing purposes
/**
* logConnectError
* Emails devs that a connection error has occured and then generates .log file
*
* @param string $class Class Name
* @param string $method Function Name
* @param string $message Error Message
* @param array $trace Exception Trace
*/
public static function logConnectError($class, $method, $message, $trace) {
$path = self::createFile($class);
$headers = array('server'=>$path[1],'method'=>$method,'path'=>$path[2]);
$mail = false;/*Mail::send(['html' => 'emails.errors.pdoconnectexception'],$headers, function($m) {
$m->from('');
$m->to('')->subject('CRITICAL: PDOConnectException');
});*/
date_default_timezone_set('America/New_York');
$message = "Error in $class $method \n Error logged at: " . date('m/d/Y h:i:s a') .
"\n Email sent to Devs: $mail \n Error logged on IP: $path[0] \n Error Message: $message" .
"\n Error Trace: \n" . json_encode($trace) . "\n ------------------------------------- \n";
error_log($message,3,$path[2]);
}
/**
* logQueryError
* Emails devs that a connection error has occured and then generates .log file
*
* @param string $class Class Name
* @param string $method Function Name
* @param QueryException $exception Exception object to retrieve trace, message, and SQL
* @param DBManager $db Database object to retrieve errors
* @param array $params urlId and projectId
*/
public static function logQueryError($class, $method, $exception, $db, $params) {
$path = self::createFile($class);
$trace = $exception->getTrace();
$errorcode = $db->getErrorCode();
$errorinfo = $db->getErrorInfo();
$ip = $_SERVER['REMOTE_ADDR'];
$message = $exception->getMessage();
$sql = $exception->getQuery();
$headers = array('trace'=>$trace,'errorcode'=>$errorcode,'erorrinfo'=>$errorinfo,'ip'=>$ip,'message'=>$message,'sql'=>$sql,'params'=>$params);
$mail = false;/*Mail::send(['html' => 'emails.errors.pdoqueryexception'],$headers, function($m) {
$m->from('');
$m->to('')->subject('PDOQueryException WebbugRedirect');
});*/
date_default_timezone_set('America/New_York');
$message = "Error in $class $method \n Error logged at: " . date('m/d/Y h:i:s a') .
"\n Email sent to Devs: $mail \n Error logged on IP: $path[0] \n Error Message: $message" .
"\n Error Trace: \n" . json_encode($trace) . "\n ------------------------------------- \n";
error_log($message,3,$path[2]);
}
/**
* createFile
* Executes common processes for logQueryError and logConnectionError.
*
* @param string $class Class Name
* @return array
*/
private static function createFile($class) {
$remote_addr = $_SERVER['REMOTE_ADDR'];
$server_addr = getenv('SERVER_ADDR');
$index = strpos($class,'\\');
$path = '../storage/logs/' . basename($class) . '_' . date('m-d-Y') . '.log';
if(!file_exists($path)) {
$file = fopen($path,'w');
fclose($file);
}
return array($remote_addr,$server_addr,$path);
}
As always, thanks for the feedback! Looking forward to some responses!