Skip to content

Commit

Permalink
WR #439133: PR changes and updated the tests to be more meaningful
Browse files Browse the repository at this point in the history
Added changes asked by Mark on PR and also made some changes to
the code to have more meaningful unit tests
  • Loading branch information
waleedhassan5 committed Oct 16, 2024
1 parent 4b3bfa0 commit 62bac8a
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 260 deletions.
60 changes: 60 additions & 0 deletions classes/suppression_list.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle. If not, see <http://www.gnu.org/licenses/>.

namespace tool_emailutils;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->libdir . '/csvlib.class.php');

/**
* Class for handling suppression list operations.
*
* @package tool_emailutils
* @copyright 2024 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class suppression_list {

/**
* Generate CSV content for the suppression list.
*
* @return string The CSV content.
* @throws \dml_exception
*/
public static function generate_csv(): string {
global $DB;

$csvexport = new \csv_export_writer('comma');

// Add CSV headers.
$csvexport->add_data(['Email', 'Reason', 'Created At']);

// Fetch suppression list from database.
$suppressionlist = $DB->get_records('tool_emailutils_suppression');

// Add suppression list data to CSV.
foreach ($suppressionlist as $item) {
$csvexport->add_data([
$item->email,
$item->reason,
$item->created_at,
]);
}

return $csvexport->print_csv_data(true);
}
}
119 changes: 61 additions & 58 deletions classes/task/update_suppression_list.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@
* Scheduled task for updating the email suppression list.
*
* @package tool_emailutils
* @copyright 2019 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @copyright 2024 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Waleed ul hassan <[email protected]>
*/

namespace tool_emailutils\task;

defined('MOODLE_INTERNAL') || die();

/**
* Scheduled task class for updating the email suppression list.
Expand All @@ -34,12 +33,15 @@
* the local database with this information.
*/
class update_suppression_list extends \core\task\scheduled_task {
/** @var \Aws\SesV2\SesV2Client|null */
protected $sesclient = null;

/**
* Return the task's name as shown in admin screens.
*
* @return string The name of the task.
*/
public function get_name() {
public function get_name(): string {
return get_string('task_update_suppression_list', 'tool_emailutils');
}

Expand All @@ -51,7 +53,7 @@ public function get_name() {
*
* @return void
*/
public function execute() {
public function execute(): void {
global $DB;

$suppressionlist = $this->fetch_aws_ses_suppression_list();
Expand Down Expand Up @@ -79,80 +81,72 @@ public function execute() {
*
* @return array The fetched suppression list.
*/
protected function fetch_aws_ses_suppression_list() {
global $CFG;
require_once($CFG->dirroot . '/local/aws/sdk/aws-autoloader.php');
require_once($CFG->dirroot . '/local/aws/sdk/Aws/SesV2/SesV2Client.php');

$awsregion = get_config('tool_emailutils', 'aws_region');
$awskey = get_config('tool_emailutils', 'aws_key');
$awssecret = get_config('tool_emailutils', 'aws_secret');

if (empty($awsregion) || empty($awskey) || empty($awssecret)) {
$this->log_error('AWS credentials are not configured. Please set them in the plugin settings.');
return [];
protected function fetch_aws_ses_suppression_list(): array {
if (!$this->sesclient) {
$this->sesclient = $this->create_ses_client();
}

try {
$sesv2 = new \Aws\SesV2\SesV2Client([
'version' => 'latest',
'region' => $awsregion,
'credentials' => [
'key' => $awskey,
'secret' => $awssecret,
],
'retries' => [
'mode' => 'adaptive',
'max_attempts' => 10,
],
]);

$suppressionlist = [];
$params = ['MaxItems' => 100]; // Reduced from 1000 to 100 to lower the chance of rate limiting.
$params = ['MaxItems' => 100];

do {
$retries = 0;
$maxretries = 5;
$delay = 1;

while ($retries < $maxretries) {
try {
$result = $sesv2->listSuppressedDestinations($params);
break; // If successful, exit the retry loop.
} catch (\Aws\Exception\AwsException $e) {
if ($e->getAwsErrorCode() === 'TooManyRequestsException') {
$retries++;
if ($retries >= $maxretries) {
$this->log_error('Max retries reached for AWS SES API call: ' . $e->getMessage());
return []; // Return empty array after max retries.
}
$this->log_error("Rate limit hit, retrying in {$delay} seconds...");
sleep($delay); // Wait before retrying.
$delay *= 2; // Exponential backoff.
} else {
$this->log_error('AWS SES Error: ' . $e->getMessage());
return []; // Return empty array for other AWS exceptions.
}
}
}
$result = $this->sesclient->listSuppressedDestinations($params);
foreach ($result['SuppressedDestinationSummaries'] as $item) {
$suppressionlist[] = [
'email' => $item['EmailAddress'],
'reason' => $item['Reason'],
'created_at' => $item['LastUpdateTime']->format('Y-m-d H:i:s'),
];
}

$params['NextToken'] = $result['NextToken'] ?? null;
} while ($params['NextToken']);

return $suppressionlist;
} catch (\Exception $e) {
$this->log_error('Unexpected error: ' . $e->getMessage());
$this->log_error('Error fetching suppression list: ' . $e->getMessage());
return [];
}
}

/**
* Create an SES client instance.
*
* @return \Aws\SesV2\SesV2Client
*/
protected function create_ses_client(): \Aws\SesV2\SesV2Client {
global $CFG;

if (!class_exists('\Aws\SesV2\SesV2Client')) {
if (file_exists($CFG->dirroot . '/local/aws/sdk/aws-autoloader.php')) {
require_once($CFG->dirroot . '/local/aws/sdk/aws-autoloader.php');
} else {
throw new \Exception('AWS SDK not found.');
}
}

$awsregion = get_config('tool_emailutils', 'aws_region');
$awskey = get_config('tool_emailutils', 'aws_key');
$awssecret = get_config('tool_emailutils', 'aws_secret');

if (empty($awsregion) || empty($awskey) || empty($awssecret)) {
throw new \Exception('AWS credentials are not configured.');
}

return new \Aws\SesV2\SesV2Client([
'version' => 'latest',
'region' => $awsregion,
'credentials' => [
'key' => $awskey,
'secret' => $awssecret,
],
'retries' => [
'mode' => 'adaptive',
'max_attempts' => 10,
],
]);
}

/**
* Log an error message, printing to console if running via CLI.
*
Expand All @@ -162,10 +156,19 @@ protected function fetch_aws_ses_suppression_list() {
* @param string $message The error message to log.
* @return void
*/
private function log_error($message) {
private function log_error(string $message): void {
if (CLI_SCRIPT) {
mtrace($message);
}
debugging($message);
}
}

/**
* Set the SES client (for testing purposes).
*
* @param \Aws\SesV2\SesV2Client $client
*/
public function set_ses_client(\Aws\SesV2\SesV2Client $client): void {
$this->sesclient = $client;
}
}
6 changes: 2 additions & 4 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,14 @@
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="email" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="reason" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="created_at" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="created_at" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false"
COMMENT="Timestamp from AWS SES API, different from timecreated. Represents when the email was added to the suppression list."/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="email" UNIQUE="true" FIELDS="email"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
4 changes: 2 additions & 2 deletions db/tasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* Definition of Email utils scheduled tasks.
*
* @package tool_emailutils
* @copyright 2019 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @copyright 2024 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Waleed ul hassan <[email protected]>
*/
Expand All @@ -29,7 +29,7 @@
[
'classname' => 'tool_emailutils\task\update_suppression_list',
'blocking' => 0,
'minute' => '0',
'minute' => 'R',
'hour' => '3',
'day' => '*',
'month' => '*',
Expand Down
18 changes: 12 additions & 6 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@
* Email utils plugin upgrade code.
*
* @package tool_emailutils
* @copyright 2019 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @copyright 2024 onwards Catalyst IT {@link http://www.catalyst-eu.net/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Waleed ul hassan <[email protected]>
*/

defined('MOODLE_INTERNAL') || die();

/**
* Upgrade script for the email utilities tool plugin.
*
* This function is executed during the plugin upgrade process.
* It checks the current version of the plugin and applies
* necessary upgrades, such as creating new database tables
* or modifying existing structures.
*
* @param int $oldversion The version we are upgrading from.
* @return bool Always returns true.
*/
function xmldb_tool_emailutils_upgrade($oldversion) {
global $DB;
$dbman = $DB->get_manager();
Expand All @@ -45,9 +54,6 @@ function xmldb_tool_emailutils_upgrade($oldversion) {
// Adding keys to table tool_emailutils_suppression.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);

// Adding indexes to table tool_emailutils_suppression.
$table->add_index('email', XMLDB_INDEX_UNIQUE, ['email']);

// Conditionally launch create table for tool_emailutils_suppression.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
Expand Down
6 changes: 3 additions & 3 deletions lang/en/tool_emailutils.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@
$string['password'] = 'Password';
$string['password_help'] = 'HTTP Basic Auth Password - Leave empty if you\'re not changing the password';
$string['pluginname'] = 'Email utilities';
$string['postmastertools'] = 'Post master tools';
$string['postmastergoogletoken'] = 'Post master tools google-site-verification';
$string['postmastergoogletoken_help'] = 'This is the entire TXT DNS record and looks similar to <code>google-site-verification=abcdef123456789abcdef</code>';
$string['postmastertools'] = 'Post master tools';
$string['privacy:metadata:tool_emailutils_list'] = 'Information.';
$string['privacy:metadata:tool_emailutils_list:updatedid'] = 'The ID of updated user.';
$string['privacy:metadata:tool_emailutils_list:userid'] = 'The ID of the user.';
$string['resetbounces'] = 'Reset the number of bounces';
$string['selectoractive'] = 'Active selector';
$string['selectoractivate'] = 'Activate key pair';
$string['selectoractivateconfirm'] = 'This will set $CFG->emaildkimselector to this selector and it will be used for signing outgoing emails.';
$string['selectoractive'] = 'Active selector';
$string['selectoractivated'] = 'Selector was activated';
$string['selectorcreate'] = 'Create a new domain:selector certificate pair';
$string['selectorcreated'] = 'A new certificate pair has been created';
Expand All @@ -102,4 +102,4 @@
$string['suppressionlistdesc'] = 'Download the email suppression list from AWS SES for your account.';
$string['task_update_suppression_list'] = 'Update email suppression list';
$string['username'] = 'Username';
$string['username_help'] = 'HTTP Basic Auth Username';
$string['username_help'] = 'HTTP Basic Auth Username';
34 changes: 20 additions & 14 deletions settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,26 @@

// Add AWS credentials settings.

$settings->add(new admin_setting_configtext('tool_emailutils/aws_region',
get_string('aws_region', 'tool_emailutils'),
get_string('aws_region_desc', 'tool_emailutils'),
'', PARAM_TEXT));

$settings->add(new admin_setting_configtext('tool_emailutils/aws_key',
get_string('aws_key', 'tool_emailutils'),
get_string('aws_key_desc', 'tool_emailutils'),
'', PARAM_TEXT));

$settings->add(new admin_setting_configpasswordunmask('tool_emailutils/aws_secret',
get_string('aws_secret', 'tool_emailutils'),
get_string('aws_secret_desc', 'tool_emailutils'),
''));
$settings->add(new admin_setting_configtext(
'tool_emailutils/aws_region',
new lang_string('aws_region', 'tool_emailutils'),
new lang_string('aws_region_desc', 'tool_emailutils'),
'', PARAM_TEXT)
);

$settings->add(new admin_setting_configtext(
'tool_emailutils/aws_key',
new lang_string('aws_key', 'tool_emailutils'),
new lang_string('aws_key_desc', 'tool_emailutils'),
'', PARAM_TEXT)
);

$settings->add(new admin_setting_configpasswordunmask(
'tool_emailutils/aws_secret',
new lang_string('aws_secret', 'tool_emailutils'),
new lang_string('aws_secret_desc', 'tool_emailutils'),
'')
);

$ADMIN->add('tool_emailutils', $settings);
}
Loading

0 comments on commit 62bac8a

Please sign in to comment.