Continuing on my quest to make sure that my application is developed strong, securely, and efficiently, I've updated my code as suggested in the previous question.
To start, I've implemented a User_test class (to be renamed once it is full implemented and designed). I have not yet added value verification to the constructor.
Any suggestions as to the changes of my code as this project has progressed is appreciated. It is worth noting that I am working on redesigning my database. Therefore, the code may drastically change on the next iteration based on the new design.
User_test
private $id;
private $username;
private $email;
private $firstName;
private $lastName;
private $uniqueURLId;
private $password;
private $mostRecentProject;
private $previousProject;
private $lastProject;
private $date;
/**
* User_test constructor.
* @param $user
*/
public function __construct($user)
{
$this->id = $user['USR_UserId']; //required
$this->username = $user['USR_Username']; //required
$this->email = $user['USR_Email']; //required
$this->firstName = $user['USR_FirstName']; //required
$this->lastName = $user['USR_LastName']; //required
$this->uniqueURLId = $user['USR_UniqueURLId'];
$this->password = $user['USR_Password']; //required
$this->mostRecentProject = $user['USR_ProjectMostRecent'];
$this->previousProject = $user['USR_ProjectPrevious'];
$this->lastProject = $user['USR_ProjectLast'];
}
/**
* checkURLId
* Checks if UniqueURLId is null and sets it if it is.
*
* @param int $projectId Integer ID referencing specific project to be concatenated onto the URLId
*/
private function checkURLId($projectId) {
if(is_null($this->uniqueURLId)) {
$db = new DBManager();
$this->uniqueURLId = RandomObjectGeneration::random_str(15) . $projectId;
$sql = "UPDATE gaig_users.users SET USR_UniqueURLId=? WHERE USR_UserId=?;";
$bindings = array($this->uniqueURLId,$this->id);
$db->query($sql,$bindings);
}
}
/**
* pushUser
* Pushes $this onto the provided array if it is valid.
*
* @param array $validUsers Array of User_test objects
* @param int $periodInWeeks Period to check in validation of user
* @param TemplateConfiguration $templateConfig Template Configuration for validation
*/
public function pushUser($validUsers, $periodInWeeks, TemplateConfiguration $templateConfig) {
try {
$this->checkURLId($templateConfig->getProjectId());
if($this->isValid($periodInWeeks,$templateConfig)) {
$validUsers[] = $this;
}
} catch(Exception $e) {
}
}
/**
* isValid
* Verifies the user is valid according to the verification algorithm defined in the check... functions.
*
* @param int $periodInWeeks Period to check in validation of user
* @param TemplateConfiguration $templateConfig Template Configuration for validation
* @return bool
*/
private function isValid($periodInWeeks, TemplateConfiguration $templateConfig) {
try {
$db = new DBManager();
$sql = "
SELECT MAX(SML_SentTimestamp) AS 'timestamp_check'
FROM gaig_users.sent_email
WHERE SML_UserId = ? AND SML_ProjectName = ?;";
$bindings = array($this->id,$this->mostRecentProject);
$data = $db->query($sql,$bindings);
if($data->rowCount() > 0) {
$result = $data->fetch();
$this->date = date('Y-m-d',strtotime('-' . $periodInWeeks . ' weeks')) . ' 00:00:00';
if($this->checkPeriod($this->date,$result['timestamp_check'])) {
return true;
}
$sql = "SELECT * FROM gaig_users.projects WHERE PRJ_ProjectId = ?;";
$data = $db->query($sql,array($this->mostRecentProject));
$mostRecentProj = new Project($data->fetch());
$newComplexity = $templateConfig->getTemplateComplexityType();
$newTarget = $templateConfig->getTemplateTargetType();
if($this->checkMRP($mostRecentProj,$newComplexity,$newTarget)) {
return false;
}
$data = $db->query($sql,array($this->previousProject));
$previousProj = new Project($data->fetch());
if($this->checkPP($mostRecentProj,$previousProj,$newComplexity)) {
return false;
}
$data = $db->query($sql,array($this->lastProject));
$lastProj = new Project($data->fetch());
if($this->checkLP($mostRecentProj,$previousProj,$lastProj,$newTarget)) {
return false;
}
}
return true;
} catch(Exception $e) {
//unsure how to manage any exceptions thrown yet, if at all. further design to come
}
}
/**
* checkPeriod - Verification Algorithm
* Verifies if the period is outside of periodInWeeks zone.
*
* @param string $date Date in format 'Y-m-d h:i:s'
* @param string $timestamp Date retrieved from PDOStatement
* @return bool
*/
private function checkPeriod($date,$timestamp) {
return $timestamp <= $date;
}
/**
* checkMRP - Verification Algorithm
* Checks the Most Recent Project to see if identical.
*
* @param Project $mrp Project object representing the Most Recent Project
* @param string $complexity Complexity type of requested template
* @param string $target Target type of requested template
* @return bool
*/
private function checkMRP(Project $mrp, $complexity, $target) {
return $complexity == $mrp->getTemplateComplexityType() &&
$target == $mrp->getTemplateTargetType();
}
/**
* checkPP - Verification Algorithm
* Checks the Previous Project and Most Recent Project for identical complexity type.
*
* @param Project $mrp Project object representing the Most Recent Project
* @param Project $pp Project object representing the Previous Project
* @param string $complexity Complexity type of requested template
* @return bool
*/
private function checkPP(Project $mrp, Project $pp, $complexity) {
return !is_null($pp) &&
$complexity == $mrp->getTemplateComplexityType() &&
$complexity == $pp->getTemplateComplexityType();
}
/**
* checkLP - Verification Algorithm
* Checks the Last Project, Previous Project, and Most Recent Project for identical target type.
*
* @param Project $mrp Project object representing the Most Recent Project
* @param Project $pp Project object representing the Previous Project
* @param Project $lp Project object representing the Last Project
* @param string $target Target type of requested template
* @return bool
*/
private function checkLP(Project $mrp, Project $pp, Project $lp, $target) {
return !is_null($lp) &&
!is_null($pp) &&
$target == $mrp->getTemplateTargetType() &&
$target == $pp->getTemplateTargetType() &&
$target == $lp->getTemplateTargetType();
}
public function getLastName() {
return $this->lastName;
}
public function getUsername() {
return $this->username;
}
public function getUniqueURLId() {
return $this->uniqueURLId;
}
public function getEmail() {
return $this->email;
}
I have not yet designed a UserCollection class. So right now I simply pass an array of User_test objects to the Email class inside of the EmailConfiguration object. This array is already verified as I use the TemplateConfiguration object to verify all users when the EmailConfiguration object is instantiated in PhishingController.
TemplateConfiguration Validation Function
/**
* getValidUsers
* Retrieves all users from the database and validates them through the User_test object.
*
* @param array $returnUsers Array of User_test objects
* @param int $periodInWeeks Period to check for instant sending of email
* @return array
*/
public function getValidUsers($returnUsers, $periodInWeeks) {
$db = new DBManager();
$sql = "SELECT * FROM gaig_users.users;";
$users = $db->query($sql,array(),array('\PDO::ATTR_CURSOR'),array('\PDO::CURSOR_SCROLL'));
$usersIterator = new PDOIterator($users);
foreach($usersIterator as $user) {
$tempUser = new User_Test($user);
$tempUser->pushUser($returnUsers,$periodInWeeks,$this);
}
return $returnUsers;
}
PhishingController Instantiation and Email Execution
/**
* sendEmail
* Function mapped to Laravel route. Defines variable arrays and calls Email Class executeEmail.
*
* @param Request $request Request object passed via AJAX from client.
*/
public function sendEmail(Request $request) {
try {
$templateConfig = new TemplateConfiguration(
array(
'templateName'=>$request->input('emailTemplate'),
'companyName'=>$request->input('companyName'),
'projectName'=>$request->input('projectData')['projectName'],
'projectId'=>intval($request->input('projectData')['projectId'])
)
);
$periodInWeeks = 4;
$users = array();
$emailConfig = new EmailConfiguration(
array(
'host'=>$request->input('hostName'),
'port'=>$request->input('port'),
'authUsername'=>$request->input('username'),
'authPassword'=>$request->input('password'),
'fromEmail'=>$request->input('fromEmail'),
'subject'=>$request->input('subject'),
'users'=>$templateConfig->getValidUsers($users,$periodInWeeks)
)
);
Email::executeEmail($emailConfig,$templateConfig);
} catch(ConfigurationException $ce) {
//will be doing something here - what still has yet to be defined (likely just log the exception)
} catch(EmailException $ee) {
//will be doing something here - what still has yet to be defined (likely just log the exception)
}
}
private static $templateConfig;
private static $emailConfig;
/**
* executeEmail
* Public-facing method to send an email to a database of users if they are a valid recipient.
*
* @param EmailConfiguration $emailConfig Email Configuration object containing required information to send an email
* @param TemplateConfiguration $templateConfig Template Configuration object containing required information to build a template
* @throws EmailException Custom Exception to embody any exceptions thrown in this class
*/
public static function executeEmail(
EmailConfiguration $emailConfig,
TemplateConfiguration $templateConfig)
{
self::setTemplateConfig($templateConfig);
self::setEmailConfig($emailConfig);
try {
foreach($emailConfig->getUsers() as $user) {
self::sendEmail($user);
self::updateUserProjects($user);
}
} catch(Exception $e) {
throw new EmailException(__CLASS__ . ' Exception',0,$e);
}
}
/**
* updateUserProjects
* Updates the user with the newest project and rotates the old projects down one.
*
* @param array $user User array extracted from PDOStatement
*/
private function updateUserProjects($user) {
$db = new DBManager();
$sql = "UPDATE gaig_users.users SET USR_ProjectMostRecent=?, USR_ProjectPrevious=?,
USR_ProjectLast=? WHERE USR_Username=?;";
$bindings = array(self::$templateConfig->getProjectName(),
$user['USR_ProjectMostRecent'],
$user['USR_ProjectPrevious'],
$user['USR_Username']
);
$db->query($sql,$bindings);
}
/**
* sendEmail
* Sends them an email to the specified user.
*
* @param User_test $user User object
* @throws FailureException
*/
private static function sendEmail($user) {
$templateData = array(
'companyName'=>self::$templateConfig->getCompanyName(),
'projectName'=>self::$templateConfig->getProjectName(),
'projectId'=>self::$templateConfig->getProjectId(),
'lastName'=>$user->getLastName(),
'username'=>$user->getUsername(),
'urlId'=>$user->getUniqueURLId()
);
$subject = self::$emailConfig->getSubject();
$from = self::$emailConfig->getFromEmail();
$to = $user->getEmail();
$mailResult = Mail::send(
['html' => self::$templateConfig->getTemplate()],
$templateData,
function($m) use ($from, $to, $subject) {
$m->from($from);
$m->to($to)
->subject($subject);
}
);
if(!$mailResult) {
throw new FailureException('Email failed to send to ' . $to . ', from ' . $from);
}
}
private static function setTemplateConfig(TemplateConfiguration $templateConfig) {
self::$templateConfig = $templateConfig;
}
private static function setEmailConfig(EmailConfiguration $emailConfig) {
self::$emailConfig = $emailConfig;
}
I have also extrapolated the random_str function to a library class.
Libraries
class RandomObjectGeneration
{
/**
* random_str
* Generates a random string.
*
* @param int $length Length of string to be returned
* @param string $keyspace Allowed characters to be used in string
* @return string
*/
public static function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
if(is_null($length) || !is_numeric($length)) {
throw new Exception();
}
$str = '';
$max = mb_strlen($keyspace) - 1;
for ($i = 0; $i < $length; ++$i) {
$str .= $keyspace[random_int(0, $max)];
}
return $str;
}
}