@Pimgd gave some good feedback on the previous post. I've made the changes that I think best suit my application and am looking to see what people think of this implementation.
PhishingController
/**
* 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) {
$fromEmail = $request['fromEmail'];
$fromPass = $request['fromPass'];
$host = $request['hostName'];
$port = $request['port'];
$emailSettings = array($host,$port,$fromEmail,$fromPass);
$emailTemplate = 'emails.' . $request['emailTemplate'];
$emailTemplateType = substr($request['emailTemplate'],0,3);
$emailTemplateTarget = substr($request['emailTemplate'],3,1);
$template = array($emailTemplate,$emailTemplateType,$emailTemplateTarget);
$period = 4;
$subject = $request['subject'];
$projectName = $request['projectName'];
$projectId = intval($projectName,strpos($projectName,'_'));
$projectName = substr($projectName,0,strpos($projectName,'_')-1);
$companyName = $request['companyName'];
$params = array($period,$projectName,$projectId,$companyName,$subject);
try {
Email::executeEmail($emailSettings,$template,$params);
} catch(OutOfBoundsException $oobe) {
//mail server settings not valid
} catch(FailureException $fee) {
//email failed to be sent to server
} catch(\PDOException $pdoe) {
DBManager::logConnectError(__CLASS__,__FUNCTION__,$pdoe->getMessage(),$pdoe->getTrace());
} catch(QueryException $qe) {
DBManager::logQueryError(__CLASS__,__FUNCTION__,$qe);
}
}
/**
* executeEmail
* Public-facing method to send an email to a database of users if they are a valid recipient.
*
* @param array $emailSettings Host, port, username, and password variables for the mail server
* @param array $template Email Template, Template Type, Template Target Type for user validation
* string Email Template Path to the blade.php template file from the views directory
* string Template Type Specifies whether the email is an Advanced (adv) or Basic (bsc) scam
* string Template Target Type Specifies whether the email is a Targeted (T) or Generic (G) scam
* @param array $params Period, Project Name, Project ID, From Email Address, Company Name, Subject
* int Period Number of weeks to check back for recipient validation
* string Project Name Name of this project
* int Project Id ID of this project
* string From Email Address Email to be sent from
* string Company Name Name of company sponsoring this awareness test
* string Subject Subject of email
* @throws OutOfBoundsException Thrown from setEmailEnvironmentSettings if a setting is not valid
* @throws FailureException Thrown from sendEmail() if mail fails to be given to mail server
* @throws \PDOException Thrown from DBManager->query()
* @throws QueryException Thrown from DBManager->query()
*/
public static function executeEmail($emailSettings, $template, $params) {
self::setEmailEnvironmentSettings($emailSettings);
$db = new DBManager();
$sql = "SELECT * FROM gaig_users.users;";
$users = $db->query($sql,array(),array('\PDO::ATTR_CURSOR'),array('\PDO::CURSOR_SCROLL'));
$userIterator = new PDOIterator($users);
foreach($userIterator as $user) {
if(self::validateUser($template[1],$template[2],$user,$params[0])) {
$to = $user['USR_Email'];
$urlId = self::getUrlId($user);
$companyName = $params[3];
$subject = $params[4];
if(is_null($params[3])) {
$companyName = 'your organization';
}
if(is_null($params[4])) {
$subject = 'Corporate Communication';
}
$headers = array('companyName'=>$companyName,'projectName'=>$params[1],'projectId'=>$params[2],
'lastName'=>$user['USR_LastName'],'username'=>$user['USR_Username'],'urlId'=>$urlId);
self::sendEmail($template[0],$headers,$to,getenv('MAIL_USERNAME'),$subject);
$project_new = $params[6] . '-' . $params[4];
$projects = array($project_new,$user['USR_ProjectMostRecent'],$user['USR_ProjectPrevious']);
self::updateUserProjects($projects,$user);
}
}
}
/**
* setEmailEnvironmentSettings
* Checks if the settings are valid settings, then sets or returns an exception.
*
* @param array $emailSettings Host, port, username, and password variables for the mail server
* @throws OutOfBoundsException
*/
private function setEmailEnvironmentSettings($emailSettings) {
$pattern = ';(?:https?://)?(?:[a-zA-Z0-9.-]+?\.(?:com|net|org|gov|edu|mil)|\d+\.\d+\.\d+\.\d+);';
if(!preg_match($pattern,$emailSettings[0]) || !filter_var($emailSettings[1],FILTER_VALIDATE_INT) ||
!filter_var($emailSettings[2],FILTER_VALIDATE_EMAIL)) {
$message = '';
if(!preg_match($pattern,$emailSettings[0])) {
$message .= 'Host is not a valid host name or IP address. host=' . $emailSettings[0] . '\n';
}
if(!filter_var($emailSettings[1],FILTER_VALIDATE_INT)) {
$message .= 'Port is not a valid integer. port=' . $emailSettings[1] . '\n';
}
if(!filter_var($emailSettings[2],FILTER_VALIDATE_EMAIL)) {
$message .= 'Username is not a valid email address. username=' . $emailSettings[2] . '\n';
}
throw new OutOfBoundsException($message);
}
putenv("MAIL_HOST=$emailSettings[0]");
putenv("MAIL_PORT=$emailSettings[1]");
putenv("MAIL_USERNAME=$emailSettings[2]");
putenv("MAIL_PASSWORD=$emailSettings[3]");
}
/**
* validateUser
* Function checks if the specified user has not received a test within the specified duration,
* if the template type is the same for the last two project participants, if the template target
* is the same for last three project participants, or if the last project is identical to the new project.
*
* @param string $templateType Specifies whether the email is an Advanced (adv) or Basic (bsc) scam
* @param string $templateTarget Specifies whether the email is a Targeted (T) or Generic (G) scam
* @param array $user Associative Array containing the fields associated to the user
* @param int $period Number of weeks to check back for recipient validation
* @return bool
*/
private function validateUser($templateType,$templateTarget,$user,$period) {
$db = new DBManager();
$date = date('Y-m-d',strtotime('-' . $period . 'weeks')) . '00:00:00';
$sql = "SELECT max(SML_AccessTimestamp) as 'timestamp_check' from gaig_users.sent_email where SML_UserId = ? and SML_ProjectName = ?;";
$bindings = array($user['USR_UserId'],$user['USR_ProjectMostRecent']);
$timestampData = $db->query($sql,$bindings);
$result = $timestampData->fetch(\PDO::FETCH_ASSOC);
if(!filter_var($user['USR_Email'],FILTER_VALIDATE_EMAIL)) {
$this->badEmailAddressWarning(['USR_Username'] . ' has a bad email address. email=' . $user['USR_Email']);
return false;
}
if($result['timestamp_check'] <= $date) {
return true;
} else if($templateType == substr($user['USR_ProjectMostRecent'],-5,3) &&
$templateType == substr($user['USR_ProjectPrevious'],-5,3)) {
return false;
} else if($templateTarget == substr($user['USR_ProjectMostRecent'],-2,1) &&
$templateTarget == substr($user['USR_ProjectPrevious'],-2,1) &&
$templateTarget == substr($user['USR_ProjectLast'],-2,1)) {
return false;
} else if($templateType.$templateTarget ==
substr($user['USR_ProjectMostRecent'],strpos($user['USR_ProjectMostRecent'],'-')+1,4)) {
return false;
}
return true;
}
/**
* getUrlId
* Generates or retrieves the UniqueURLId of the passed user.
*
* @param array $user User array extracted from PDOStatement
* @return string
*/
private function getUrlId($user) {
$db = new DBManager();
if(!is_null($user['USR_UniqueURLId'])) {
$urlId = $user['USR_UniqueURLId'];
} else {
$urlId = $this->random_str(15);
$sql = "UPDATE gaig_users.users SET USR_UniqueURLId=?;";
$bindings = array($urlId);
$db->query($sql,$bindings);
}
return $urlId;
}
/**
* updateUserProjects
* Updates the user with the newest project and rotates the old projects down one.
*
* @param array $projects Most Recent Project, Previous Project, Oldest Project
* @param array $user User array extracted from PDOStatement
*/
private function updateUserProjects($projects,$user) {
$db = new DBManager();
$sql = "UPDATE gaig_users.users SET USR_ProjectMostRecent=?, USR_ProjectPrevious=?,
USR_ProjectLast=? WHERE USR_Username=?;";
$bindings = array($projects[0],$projects[1],$projects[2],$user['USR_Username']);
$db->query($sql,$bindings);
}
/**
* sendEmail
* Iterates through the PDO Result Set of users. Calls validRecipientAlgo to validate user. Sends email if
* valid and updates user if valid.
* @param array $params Required parameters to pass to the email template
* @param string $from Email to be sent from
* @param string $subject Subject of email
* @throws FatalErrorException
*/
private function sendEmail($template, $headers, $to, $from, $subject) {
if(!Mail::send(['html' => $template],$headers, function($m) use ($from, $to, $subject) {
$m->from($from);
$m->to($to)->subject($subject);
})) {
throw new FailureException('Email failed to send to ' . $to . ' from ' . $from);
}
}
/**
* 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
*/
private function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$str = '';
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$str .= $keyspace[random_int(0, $max)];
}
return $str;
}
/**
* badEmailAddressWarning
* Logs error when a bad email address is found associated with a user
*
* @param string $message Error message to be logged
*/
private function badEmailAddressWarning($message) {
$path = '../storage/logs/badEmailAddress' . date('m-d-Y') . '.log';
if(!file_exists($path)) {
$file = fopen($path,'w');
fclose($file);
}
error_log($message,3,$path);
}
As always, thanks for the review!