From 8055771cacf5ded5337138a580a162137e32bb69 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Wed, 16 Aug 2023 17:28:42 +1000 Subject: [PATCH 1/8] Fixed code standards --- classes/event/notification_received.php | 4 +++- classes/sns_client.php | 7 +++++++ classes/sns_notification.php | 2 ++ client.php | 6 ++++++ index.php | 2 ++ lib.php | 5 +++++ version.php | 2 ++ 7 files changed, 27 insertions(+), 1 deletion(-) diff --git a/classes/event/notification_received.php b/classes/event/notification_received.php index cc5dd83..ab5c153 100644 --- a/classes/event/notification_received.php +++ b/classes/event/notification_received.php @@ -26,7 +26,9 @@ use tool_emailutils; - +/** + * Event + */ class notification_received extends \core\event\base { /** diff --git a/classes/sns_client.php b/classes/sns_client.php index c7630c5..2321561 100644 --- a/classes/sns_client.php +++ b/classes/sns_client.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Amazon SNS Client Interface + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -45,14 +47,19 @@ */ class sns_client { + /** Subscribe */ const SUBSCRIPTION_TYPE = 'SubscriptionConfirmation'; + /** Unsubscribe */ const UNSUBSCRIPTION_TYPE = 'UnsubscribeConfirmation'; + /** Notify */ const NOTIFICATION_TYPE = 'Notification'; + /** Complaint */ const COMPLAINT_TYPE = 'Complaint'; + /** Bounce */ const BOUNCE_TYPE = 'Bounce'; /** diff --git a/classes/sns_notification.php b/classes/sns_notification.php index 92808d1..dae6783 100644 --- a/classes/sns_notification.php +++ b/classes/sns_notification.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Amazon SNS Notification Class + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later diff --git a/client.php b/client.php index b25448e..f2a2b5c 100644 --- a/client.php +++ b/client.php @@ -15,6 +15,12 @@ // along with Moodle. If not, see . /** + * An email bounce complaint handler webhook + * + * This is the endpoint that AWS notifies when it receives a complaint. + * This handles the complaint by incrementing the users bounce level and + * emiting a Moodle event. + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later diff --git a/index.php b/index.php index 3de9300..05bccd4 100644 --- a/index.php +++ b/index.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Email bounce handler + * * @package tool_emailutils * @copyright 2019 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later diff --git a/lib.php b/lib.php index 839966f..12be8ed 100644 --- a/lib.php +++ b/lib.php @@ -15,12 +15,17 @@ // along with Moodle. If not, see . /** + * Lib + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Harry Barnard */ +/** + * This adds a new bulk user action to reset a persons bounce count + */ function tool_emailutils_bulk_user_actions() { return [ 'tool_ses_reset_bounces' => new action_link( diff --git a/version.php b/version.php index aefbb54..419f85f 100644 --- a/version.php +++ b/version.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Version + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later From 692b612ef7ca6d19595cfa12c15475138a338e7f Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Wed, 16 Aug 2023 17:30:23 +1000 Subject: [PATCH 2/8] Code standards fixes --- classes/complaints_list.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/classes/complaints_list.php b/classes/complaints_list.php index b091635..a4b24e9 100644 --- a/classes/complaints_list.php +++ b/classes/complaints_list.php @@ -16,6 +16,7 @@ /** * Class complaint_list + * * @package tool_emailutils * @copyright 2019 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -27,6 +28,8 @@ use renderable; /** + * Class complaint_list + * * The complaints list class is a table which can indicate if a user has exceeded the bounce threshold. */ class complaints_list extends \table_sql implements renderable { @@ -35,6 +38,7 @@ class complaints_list extends \table_sql implements renderable { * * @param string $uniqueid unique id of form. * @param \moodle_url $url url where this table is displayed. + * @param int $perpage how many items per page */ public function __construct($uniqueid, \moodle_url $url, $perpage = 100) { global $DB; @@ -91,7 +95,7 @@ public function __construct($uniqueid, \moodle_url $url, $perpage = 100) { /** * Bouncecount column. Will wrap the values in a if the value is over the computed threshold. * - * @param $data + * @param mixed $data * @return string */ public function col_bouncecount($data) { From 9b8b20d2c0d710bfced329b5010fe466c5e0672a Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Wed, 16 Aug 2023 17:33:27 +1000 Subject: [PATCH 3/8] codestandards --- classes/admin_setting_configpasswordhashed.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/classes/admin_setting_configpasswordhashed.php b/classes/admin_setting_configpasswordhashed.php index 9777933..f0b36e8 100644 --- a/classes/admin_setting_configpasswordhashed.php +++ b/classes/admin_setting_configpasswordhashed.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Hashed password formlib form element + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -23,10 +25,15 @@ namespace tool_emailutils; - +/** + * Hashed password formlib form element + */ class admin_setting_configpasswordhashed extends \admin_setting { + /** @var Min length of password */ public $minlength; + + /** @var Is the password hashed */ protected $ishashed; /** @@ -53,6 +60,10 @@ public function get_setting() { return $this->config_read($this->name); } + /** + * Writes the settings + * @param mixed $data data + */ public function write_setting($data) { // Is the password valid? $isvalid = $this->validate($data); @@ -72,7 +83,7 @@ public function write_setting($data) { /** * Validate data before storage - * @param string data + * @param string $data data * @return mixed true if ok string if error found */ public function validate($data) { @@ -85,6 +96,9 @@ public function validate($data) { /** * Return an XHTML string for the setting + * + * @param string $data data + * @param string $query * @return string Returns an XHTML string */ public function output_html($data, $query = '') { From 22f3bbaa591aca7c8ad37e00b18ca97924775350 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Fri, 11 Aug 2023 22:45:00 +1000 Subject: [PATCH 4/8] Fix install issue with empty password --- classes/admin_setting_configpasswordhashed.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/classes/admin_setting_configpasswordhashed.php b/classes/admin_setting_configpasswordhashed.php index f0b36e8..b546c97 100644 --- a/classes/admin_setting_configpasswordhashed.php +++ b/classes/admin_setting_configpasswordhashed.php @@ -74,6 +74,11 @@ public function write_setting($data) { if (empty($data)) { // Password field is empty so just reuse existing hash. $password = $this->config_read($this->name); + + // If it is null then it is a fresh install so save an empty string. + if ($password === null) { + $password = ''; + } } else { // Hash new password. $password = password_hash($data, PASSWORD_DEFAULT); @@ -87,6 +92,9 @@ public function write_setting($data) { * @return mixed true if ok string if error found */ public function validate($data) { + if ($data === '') { + return true; + } if (empty($data) || (is_string($data) && (strlen($data) >= $this->minlength))) { return true; } From 81551764880036f5c16398081c366776ba4645f5 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Sat, 12 Aug 2023 00:00:26 +1000 Subject: [PATCH 5/8] Added DKIM manager page #7 --- classes/dkim_manager.php | 225 ++++++++++++++++++++++++++++++++ classes/form/create_dkim.php | 76 +++++++++++ dkim.php | 186 ++++++++++++++++++++++++++ lang/en/tool_emailutils.php | 31 +++++ settings.php | 8 ++ styles.css | 11 ++ templates/dkimselector.mustache | 83 ++++++++++++ 7 files changed, 620 insertions(+) create mode 100644 classes/dkim_manager.php create mode 100644 classes/form/create_dkim.php create mode 100644 dkim.php create mode 100644 styles.css create mode 100644 templates/dkimselector.mustache diff --git a/classes/dkim_manager.php b/classes/dkim_manager.php new file mode 100644 index 0000000..a20c34a --- /dev/null +++ b/classes/dkim_manager.php @@ -0,0 +1,225 @@ +. + +/** + * DKIM manager + * + * This loads, verifies and can auto create DKIM pairs of certificates + * Code largely adapted from PHPMailer + * + * @package tool_emailutils + * @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Brendan Heywood + */ + +namespace tool_emailutils; + +/** + * DKIM manager + */ +class dkim_manager { + + /** @var Domain */ + protected $domain; + + /** @var Selector */ + protected $selector; + + /** @var Private key */ + protected $privatekey; + + /** @var Public key */ + protected $publickey; + + /** @var DNS record */ + protected $dnsrecord; + + /** Digest algorythm */ + const DIGEST_ALG = 'sha256'; + + /** + * Create or load the certificates for a domain and selector + * @param string $domain domain + * @param string $selector + * @param bool $autocreate Should this autocreate cert pairs if they don't exist? + */ + public function __construct($domain, $selector, $autocreate = false) { + $this->domain = $domain; + $this->selector = $selector; + + $privatekeyfile = $this->get_private_key_path(); + $publickeyfile = $this->get_public_key_path(); + + if (!file_exists($privatekeyfile) && $autocreate) { + + $this->get_base_path(true); + // Create a 2048-bit RSA key with an SHA256 digest. + $pk = openssl_pkey_new( + [ + 'digest_alg' => self::DIGEST_ALG, + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ] + ); + + // Save both keys. + openssl_pkey_export_to_file($pk, $privatekeyfile); + $details = openssl_pkey_get_details($pk); + file_put_contents($publickeyfile, $details['key']); + } + + $this->privatekey = file_get_contents($privatekeyfile); + $this->publickey = file_get_contents($publickeyfile); + } + + /** + * Get the domain file path + */ + public function get_domain_path() { + global $CFG; + return $CFG->dataroot . '/dkim/' . $this->domain . '/'; + } + + /** + * Get the domain file path + * @param bool $create auto create the directories + */ + public function get_base_path($create = false) { + $certdir = $this->get_domain_path(); + if ($create) { + mkdir($certdir, 0777, true); + } + return $certdir . '/' . $this->selector; + } + + /** + * Get the private key file path + */ + public function get_private_key_path() { + return $this->get_base_path() . '.private'; + } + + /** + * Get the public key file path + */ + public function get_public_key_path() { + return $this->get_base_path() . '.public'; + } + + /** + * Get the DNS record file path + */ + public function get_dns_record_path() { + return $this->get_base_path() . '.txt'; + } + + /** + * Get the domain the DKIM record should be stored at + */ + public function get_dns_domain() { + return "{$this->selector}._domainkey.{$this->domain}"; + } + + /** + * Get the key of the DKIM txt record + */ + public function get_dns_key() { + return $this->get_dns_domain() . ' IN TXT'; + } + + /** + * Get the value of the DKIM record + * + * This loads the public key and then stores the DNS record in a file. + */ + public function get_dns_value() { + if (!empty($this->dnsrecord)) { + return $this->dnsrecord; + } + + // TODO add support for records added by open dkim + // These do not include the public key in the normal format, only in the DNS value format. + + if (empty($this->publickey)) { + return "ERROR: Can't find public key"; + } + + $dnsvalue = 'v=DKIM1;'; + $dnsvalue .= ' h=' . self::DIGEST_ALG . ';'; // Hash algorythm. + $dnsvalue .= ' t=s;'; // No sub domains allowed. + $dnsvalue .= ' k=rsa;'; // Key type. + $dnsvalue .= ' p='; // Public key. + + $publickey = $this->publickey; + $publickey = preg_replace('/^-+.*?-+$/m', '', $publickey); // Remove PEM wrapper. + $publickey = str_replace(["\r", "\n"], '', $publickey); // Strip line breaks. + $dnsvalue .= $publickey; + + $this->dnsrecord = trim($dnsvalue); + + return $this->dnsrecord; + } + + /** + * Get a chunked version of the DKIM record + * + * Strip and split the key into smaller parts and format for DNS as many systems + * don't like long TXT entries but are OK if it's split into 255-char chunks. + */ + public function get_dns_value_chunked() { + + $rawvalue = $this->get_dns_value(); + + // Split into chunks. + $keyparts = str_split($rawvalue, 253); // Becomes 255 when quotes are included. + // Quote each chunk. + foreach ($keyparts as $keypart) { + $dnsvalue .= '"' . trim($keypart) . '" '; + } + + return $dnsvalue; + + } + /** + * Get the alternate escaped version of the DKIM record + * + * Some DNS servers don't like ;(semi colon) chars unless backslash-escaped + */ + public function get_dns_value_escaped() { + + $value = $this->get_dns_value_chunked(); + $value = str_replace(';', '\;', $value); + return $value; + + } + + /** + * Delete all info about a selector + */ + public function delete_selector() { + $privatekeyfile = $this->get_private_key_path(); + $publickeyfile = $this->get_public_key_path(); + $dnsrecordfile = $this->get_dns_record_path(); + $domaindir = $this->get_domain_path(); + + @unlink($privatekeyfile); + @unlink($publickeyfile); + @unlink($dnsrecordfile); + @rmdir($domaindir); + } + +} diff --git a/classes/form/create_dkim.php b/classes/form/create_dkim.php new file mode 100644 index 0000000..be070cf --- /dev/null +++ b/classes/form/create_dkim.php @@ -0,0 +1,76 @@ +. + +/** + * Create dkim selector form + * + * @package tool_emailutils + * @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Brendan Heywood + */ + +namespace tool_emailutils\form; + +defined('MOODLE_INTERNAL') || die; + +require_once("$CFG->libdir/formslib.php"); + +/** + * Selector form + */ +class create_dkim extends \moodleform { + + /** + * Selector + * @see moodleform::definition() + */ + public function definition() { + + global $CFG; + + $mform = $this->_form; + $noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1); + + $group = []; + + $group[] =& $mform->createElement('text', 'domain', array("size" => 20)); + $mform->setDefault("domain", $noreplydomain); + $mform->setType('domain', PARAM_HOST); + + $group[] =& $mform->createElement('text', 'selector', array("size" => 20)); + + $selector = \userdate(time(), get_string('selectordefault', 'tool_emailutils')); + $mform->setDefault("selector", $selector); + $mform->setType('selector', PARAM_HOST); + + $mform->addGroup($group, 'selector', get_string('selectorcreate', 'tool_emailutils'), '', false); + + $this->add_action_buttons(true, get_string('selectorcreatesubmit', 'tool_emailutils')); + } + + /** + * Validate + * + * @param mixed $data date + * @param mixed $files files + * @return mixed errors + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + return $errors; + } +} diff --git a/dkim.php b/dkim.php new file mode 100644 index 0000000..a4c3b91 --- /dev/null +++ b/dkim.php @@ -0,0 +1,186 @@ +. + +/** + * DKIM manager admin page + * + * @package tool_emailutils + * @copyright 2023 onwards Catalyst IT {@link http://www.catalyst-eu.net/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Brendan Heywood + */ + +use tool_emailutils\dkim_manager; + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +$baseurl = new moodle_url('/admin/tool/emailutils/dkim.php'); +$PAGE->set_url($baseurl); +admin_externalpage_setup('tool_emailutils_dkim'); + +$action = optional_param('action', '', PARAM_ALPHA); + +if ($action == 'delete') { + require_sesskey(); + $domain = required_param('domain', PARAM_TEXT); + $selector = required_param('selector', PARAM_TEXT); + $manager = new dkim_manager($domain, $selector); + $manager->delete_selector(); + redirect($baseurl, get_string('selectordeleted', 'tool_emailutils'), null, \core\output\notification::NOTIFY_SUCCESS); +} + +if ($action == 'activate') { + require_sesskey(); + $selector = required_param('selector', PARAM_TEXT); + add_to_config_log('emaildkimselector', $CFG->emaildkimselector, $selector, ''); + set_config('emaildkimselector', $selector); + redirect($baseurl, get_string('selectoractivated', 'tool_emailutils'), null, \core\output\notification::NOTIFY_SUCCESS); +} + +$form = new \tool_emailutils\form\create_dkim(); +if ($form->is_cancelled()) { + redirect($prevurl); +} else if ($fromform = $form->get_data()) { + + $domain = $fromform->domain; + $selector = $fromform->selector; + $manager = new dkim_manager($domain, $selector, true); + redirect($baseurl, get_string('selectorcreated', 'tool_emailutils'), null, \core\output\notification::NOTIFY_SUCCESS); +} + +$dkimdir = $CFG->dataroot . '/dkim/'; +$domains = scandir($dkimdir); +$domaincount = 0; +$noreplydomain = substr($CFG->noreplyaddress, strpos($CFG->noreplyaddress, '@') + 1); + +print $OUTPUT->header(); +print $OUTPUT->heading(get_string('dkimmanager', 'tool_emailutils')); + +print ""; +print ''; +foreach ($domains as $domain) { + + if (substr($domain, 0, 1) == '.') { + continue; + } + if (!is_dir($dkimdir . $domain)) { + continue; + } + + $domaincount ++; + + print ''; + print ''; + + + $selectors = scandir($dkimdir . $domain); + $selectorcount = 0; + + foreach ($selectors as $file) { + + if (substr($file, -8, 8) !== '.private') { + continue; + } + + $selector = substr($file, 0, -8); + $manager = new dkim_manager($domain, $selector); + + $context = [ + 'domain' => $domain, + 'selector' => $selector, + 'dkimurl' => new moodle_url('https://mxtoolbox.com/SuperTool.aspx', + ['action' => "dkim:$domain:$selector", 'run' => 'toolpage']), + 'dkimrawurl' => new moodle_url('https://mxtoolbox.com/SuperTool.aspx', + ['action' => "txt:$selector._domainkey.$domain"]), + 'dnskey' => $manager->get_dns_key(), + 'dnsvalue' => $manager->get_dns_value(), + 'dnsvaluechunked' => $manager->get_dns_value_chunked(), + 'dnsvalueescaped' => $manager->get_dns_value_escaped(), + 'id' => uniqid(), + ]; + + if ($CFG->emaildkimselector == $selector) { + $context['selectoractive'] = true; + } + + if ($CFG->emaildkimselector !== $selector) { + // Only give the option to delete if it is not being used. + $confirmation = new \confirm_action( + get_string('selectordeleteconfirm', 'tool_emailutils'), + null, + get_string('selectordelete', 'tool_emailutils') + ); + $context['selectordelete'] = $OUTPUT->action_link( + new moodle_url('/admin/tool/emailutils/dkim.php', [ + 'domain' => $domain, + 'selector' => $selector, + 'action' => 'delete', + 'sesskey' => sesskey()]), + get_string('selectordelete', 'tool_emailutils'), + $confirmation, + ['class' => 'btn btn-secondary btn-sm'], + new pix_icon('i/delete', '')); + + // Only give the option to make it the active select if it is not being used. + $confirmation = new \confirm_action( + get_string('selectoractivateconfirm', 'tool_emailutils'), + null, + get_string('selectoractivate', 'tool_emailutils') + ); + $context['selectoractivate'] = $OUTPUT->action_link( + new moodle_url('/admin/tool/emailutils/dkim.php', [ + 'selector' => $selector, + 'action' => 'activate', + 'sesskey' => sesskey()]), + get_string('selectoractivate', 'tool_emailutils'), + $confirmation, + ['class' => 'btn btn-secondary btn-sm'], + new pix_icon('i/star', '')); + } + + print $OUTPUT->render_from_template('tool_emailutils/dkimselector', $context); + } +} +print "
Domains / selectorsActions
'; + print '

'; + print html_writer::tag('span', "@$domain "); + if ($domain == $noreplydomain) { + print ' ' . html_writer::tag('span', get_string('domaindefaultnoreply', 'tool_emailutils'), + ['class' => 'badge badge-secondary']); + } + print '

'; + print '
'; + + $url = new moodle_url('https://mxtoolbox.com/SuperTool.aspx', ['action' => "spf:$domain", 'run' => 'toolpage']); + print get_string('mxtoolbox', 'tool_emailutils'); + print '
    '; + print "
  • SPF"; + + $url = new moodle_url('https://mxtoolbox.com/SuperTool.aspx', ['action' => "txt:$domain"]); + print "
  • Raw TXT"; + + print '
"; + +if ($domaincount == 0) { + echo $OUTPUT->notification(get_string('selectormissing', 'tool_emailutils'), \core\notification::ERROR); +} + +print html_writer::tag('div', get_string('dkimmanagerhelp', 'tool_emailutils'), ['class' => 'crap', 'style' => 'max-width: 40em']); + +$form->display(); + +echo $OUTPUT->footer(); diff --git a/lang/en/tool_emailutils.php b/lang/en/tool_emailutils.php index ae12eef..21c72bc 100644 --- a/lang/en/tool_emailutils.php +++ b/lang/en/tool_emailutils.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Lang pack + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -46,6 +48,35 @@ $string['event:notificationreceived'] = 'AWS SNS notification received'; +$string['dkimmanager'] = 'DKIM manager'; +$string['dkimmanagerhelp'] = '

This shows all DKIM key pairs / selectors available for email signing, including those made by this admin tool or put in place by external tools such as open-dkim. For most systems this is the end to end setup:

+
    +
  1. First decide and set the $CFG->noreply email as the domain of this is email is tied to the signing. +
  2. Create a new private and public key pair using a selector of your choice. The selector is arbitrary but a rough date format is a good convention. +
  3. Save the DNS record shown in this tool into your DNS server +
  4. Confirm that the DNS is in the correct shape using the MXtoolbox links +
  5. Now activate the selector you have chosen +
  6. Use the test email tool to send a real email and confirm the DKIM headers have been sent +
  7. Also confirm the DKIM headers validate using a 3rd party tools built into gmail, and most email clients +
+'; + +$string['domaindefaultnoreply'] = 'Default noreply'; + +$string['mxtoolbox'] = 'MXtoolbox'; +$string['selectoractive'] = 'Active selector'; +$string['selectoractivate'] = 'Activate selector'; +$string['selectoractivated'] = 'Selector was activated'; +$string['selectoractivateconfirm'] = 'This will set $CFG->emaildkimselector to this selector and it will be used for signing outgoing emails.'; +$string['selectorcreate'] = 'Create a new domain:selector certificate pair'; +$string['selectorcreatesubmit'] = 'Create new selector'; +$string['selectorcreated'] = 'A new certificate pair has been created'; +$string['selectordefault'] = '%Y-%m'; +$string['selectormissing'] = 'No DKIM selector certificates found'; +$string['selectordelete'] = 'Delete inactive selector'; +$string['selectordeleted'] = 'Inactive selector has been deleted'; +$string['selectordeleteconfirm'] = 'This will permanently delete this selector\'s private and public keys and is irreversable.'; + // Complaints list strings. $string['not_implemented'] = 'Not implemented yet. Search the user report for emails ending with ".b.invalid" and ".c.invalid".'; $string['bounces'] = 'For a list of bounces, visit {$a} and search for emails ending with ".b.invalid."'; diff --git a/settings.php b/settings.php index e9028d1..66d2b0d 100644 --- a/settings.php +++ b/settings.php @@ -15,6 +15,8 @@ // along with Moodle. If not, see . /** + * Add admin settings + * * @package tool_emailutils * @copyright 2018 onwards Catalyst IT {@link http://www.catalyst-eu.net/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -29,6 +31,12 @@ new lang_string('pluginname', 'tool_emailutils') )); + $ADMIN->add('email', new admin_externalpage( + 'tool_emailutils_dkim', + new lang_string('dkimmanager', 'tool_emailutils'), + new moodle_url('/admin/tool/emailutils/dkim.php') + )); + $ADMIN->add('tool_emailutils', new admin_externalpage( 'tool_emailutils_list', new lang_string('list', 'tool_emailutils'), diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..da66606 --- /dev/null +++ b/styles.css @@ -0,0 +1,11 @@ + +.path-admin-tool-emailutils .dnsrecord { + background: #eee; + font: 75% monospace; + max-width: 50em; + overflow-wrap: anywhere; + padding: 0.5em; + text-wrap: wrap; + user-select: all; +} + diff --git a/templates/dkimselector.mustache b/templates/dkimselector.mustache new file mode 100644 index 0000000..2bfac39 --- /dev/null +++ b/templates/dkimselector.mustache @@ -0,0 +1,83 @@ +{{! + 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 . +}} +{{! + @template tool_emailutils/dkimselector + + This template renders a DKIM selector table row + + Context variables required for this template: + * selectoractive If this selector is the one in use + + Example context (json): + { + "welcomemessage": "welcomemessage", + "selectoractive": true + } +}} + + + {{selector}} + {{#selectoractive}} +
{{#str}} selectoractive, tool_emailutils{{/str}} + {{/selectoractive}} + + +
+
+
+ +
+
+ {{dnskey}} +

This is the full raw txt of the DNS value:

+

{{dnsvalue}}

+
+
+ {{dnskey}} +

This is the DNS record value broken into quoted chunks of max 256 chars:

+

{{dnsvaluechunked}}

+
+
+ {{dnskey}} +

This is an escaped record value which is needed for some DNS systems:

+

{{dnsvalueescaped}}

+
+
+
+
+
+ + + {{#str}} mxtoolbox, tool_emailutils{{/str}} + +

{{{selectordelete}}}

+

{{{selectoractivate}}}

+ + From 21755ac6609fd9ffe211c9285e95ecd3398991d2 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Wed, 16 Aug 2023 17:44:18 +1000 Subject: [PATCH 6/8] More code standards fixes --- classes/complaints_list.php | 3 ++- client.php | 2 ++ reset_bounces.php | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/classes/complaints_list.php b/classes/complaints_list.php index a4b24e9..9a739e5 100644 --- a/classes/complaints_list.php +++ b/classes/complaints_list.php @@ -93,7 +93,8 @@ public function __construct($uniqueid, \moodle_url $url, $perpage = 100) { } /** - * Bouncecount column. Will wrap the values in a if the value is over the computed threshold. + * Bouncecount column. Will wrap the values in a + * if the value is over the computed threshold. * * @param mixed $data * @return string diff --git a/client.php b/client.php index f2a2b5c..11af7c8 100644 --- a/client.php +++ b/client.php @@ -30,6 +30,8 @@ use tool_emailutils\sns_client; use tool_emailutils\event\notification_received; +define('NO_MOODLE_COOKIES', true); + require_once(__DIR__ . '/../../../config.php'); if (!get_config('tool_emailutils', 'enabled')) { diff --git a/reset_bounces.php b/reset_bounces.php index 7df3ac2..29a3ee5 100644 --- a/reset_bounces.php +++ b/reset_bounces.php @@ -40,7 +40,7 @@ echo $OUTPUT->header(); echo $OUTPUT->heading(get_string('resetbounces', 'tool_emailutils')); -if ($confirm and confirm_sesskey()) { +if ($confirm && confirm_sesskey()) { list($in, $params) = $DB->get_in_or_equal($SESSION->bulk_users); $rs = $DB->get_recordset_select('user', "id $in", $params, '', 'id, ' . get_all_user_name_fields(true)); foreach ($rs as $user) { From 1e8b0a7f820cd54e55380ba787d623ae05e0787f Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Wed, 16 Aug 2023 17:54:00 +1000 Subject: [PATCH 7/8] Clean up mustache code standards --- ...dmin_setting_configpasswordhashed.mustache | 33 ++++++++++++++++++- templates/bounce_column.mustache | 32 +++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/templates/admin_setting_configpasswordhashed.mustache b/templates/admin_setting_configpasswordhashed.mustache index 9a16692..44d5b09 100644 --- a/templates/admin_setting_configpasswordhashed.mustache +++ b/templates/admin_setting_configpasswordhashed.mustache @@ -1,3 +1,34 @@ +{{! + 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 . +}} +{{! + @template tool_emailutils/admin_setting_configpasswordhashed + + This template renders a DKIM selector table row + + Context variables required for this template: + * selectoractive If this selector is the one in use + + Example context (json): + { + "minlength": 8, + "id": "myid", + "fullname": "myname" + } +}}
-
\ No newline at end of file + diff --git a/templates/bounce_column.mustache b/templates/bounce_column.mustache index c86277a..402a045 100644 --- a/templates/bounce_column.mustache +++ b/templates/bounce_column.mustache @@ -1,6 +1,36 @@ +{{! + 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 . +}} +{{! + @template tool_emailutils/admin_setting_configpasswordhashed + + This template renders a DKIM selector table row + + Context variables required for this template: + * selectoractive If this selector is the one in use + + Example context (json): + { + "overthreshhold": true, + "bouncecount": 5 + } +}} {{#overthreshold}} {{bouncecount}} {{/overthreshold}} {{^overthreshold}} {{bouncecount}} -{{/overthreshold}} \ No newline at end of file +{{/overthreshold}} From e3c99c966ed8df7c1cc38c2c7fc3d8b43c740732 Mon Sep 17 00:00:00 2001 From: Brendan Heywood Date: Wed, 16 Aug 2023 18:00:27 +1000 Subject: [PATCH 8/8] Temp disable mustache linting --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a663f92..10e27e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,3 +7,4 @@ jobs: uses: catalyst/catalyst-moodle-workflows/.github/workflows/group-39-plus-ci.yml@main with: disable_behat: true + disable_mustache: true