From c57f36a1bd047d4519d8b2e0bf42be9f6c10d3b1 Mon Sep 17 00:00:00 2001 From: Pratik Joshi Date: Wed, 23 Apr 2014 16:01:36 +0530 Subject: [PATCH] CRM-14436-fix --- CRM/Admin/Form/Preferences/Mailing.php | 12 +++-- CRM/Contact/BAO/Contact/Utils.php | 45 +++++++++++++------ CRM/Mailing/BAO/Mailing.php | 36 ++++++++++++--- CRM/Mailing/Form/Approve.php | 8 +++- CRM/Mailing/Form/Schedule.php | 8 +++- CRM/Mailing/Page/View.php | 30 +++++++++++-- .../Incremental/sql/4.5.alpha1.mysql.tpl | 7 ++- CRM/Utils/Token.php | 8 +++- settings/Mailing.setting.php | 18 +++++++- xml/schema/Mailing/Mailing.xml | 15 ++++++- 10 files changed, 154 insertions(+), 33 deletions(-) diff --git a/CRM/Admin/Form/Preferences/Mailing.php b/CRM/Admin/Form/Preferences/Mailing.php index 1f52554b7c..e21570426d 100644 --- a/CRM/Admin/Form/Preferences/Mailing.php +++ b/CRM/Admin/Form/Preferences/Mailing.php @@ -106,13 +106,20 @@ class CRM_Admin_Form_Preferences_Mailing extends CRM_Admin_Form_Preferences { 'weight' => 9, 'description' => ts('Don\'t check for presence of mandatory tokens (domain address; unsubscribe/opt-out) before sending mailings. WARNING: Mandatory tokens are a safe-guard which facilitate compliance with the US CAN-SPAM Act. They should only be disabled if your organization adopts other mechanisms for compliance or if your organization is not subject to CAN-SPAM.'), ), - 'dedupe_email_default' => + 'dedupe_email_default' => array( 'html_type' => 'checkbox', 'title' => ts('CiviMail dedupes e-mail addresses by default'), 'weight' => 10, 'description' => NULL, ), + 'hash_mailing_url' => + array( + 'html_type' => 'checkbox', + 'title' => ts('Hashed Mailing URL\'s'), + 'weight' => 11, + 'description' => 'If enabled, a randomized hash key will be used to reference the mailing URL in the mailing.viewUrl token, instead of the mailing ID', + ), ), ); @@ -143,5 +150,4 @@ class CRM_Admin_Form_Preferences_Mailing extends CRM_Admin_Form_Preferences { parent::postProcess(); } -} - +} \ No newline at end of file diff --git a/CRM/Contact/BAO/Contact/Utils.php b/CRM/Contact/BAO/Contact/Utils.php index cfc7aa9b18..b16cd9c227 100644 --- a/CRM/Contact/BAO/Contact/Utils.php +++ b/CRM/Contact/BAO/Contact/Utils.php @@ -139,9 +139,9 @@ WHERE id IN ( $idString ) } /** - * Generate a checksum for a contactID + * Generate a checksum for a $entityId of type $entityType * - * @param int $contactID + * @param int $entityId * @param int $ts timestamp that checksum was generated * @param int $live life of this checksum in hours/ 'inf' for infinite * @param string $hash contact hash, if sent, prevents a query in inner loop @@ -150,26 +150,45 @@ WHERE id IN ( $idString ) * @static * @access public */ - static function generateChecksum($contactID, $ts = NULL, $live = NULL, $hash = NULL) { - // return a warning message if we dont get a contactID + static function generateChecksum($entityId, $ts = NULL, $live = NULL, $hash = NULL, $entityType = 'contact', $hashSize = NULL) { + // return a warning message if we dont get a entityId // this typically happens when we do a message preview // or an anon mailing view - CRM-8298 - if (!$contactID) { + if (!$entityId) { return 'invalidChecksum'; } if (!$hash) { - $hash = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', - $contactID, 'hash' - ); + if ($entityType == 'contact') { + $hash = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', + $entityId, 'hash' + ); + } + elseif ($entityType == 'mailing') { + $hash = CRM_Core_DAO::getFieldValue('CRM_Mailing_DAO_Mailing', + $entityId, 'hash' + ); + } } if (!$hash) { $hash = md5(uniqid(rand(), TRUE)); - CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Contact', - $contactID, - 'hash', $hash - ); + if ($hashSize) { + $hash = substr($hash, 0, $hashSize); + } + + if ($entityType == 'contact') { + CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Contact', + $entityId, + 'hash', $hash + ); + } + elseif ($entityType == 'mailing') { + CRM_Core_DAO::setFieldValue('CRM_Mailing_DAO_Mailing', + $entityId, + 'hash', $hash + ); + } } if (!$ts) { @@ -185,7 +204,7 @@ WHERE id IN ( $idString ) $live = 24 * $days; } - $cs = md5("{$hash}_{$contactID}_{$ts}_{$live}"); + $cs = md5("{$hash}_{$entityId}_{$ts}_{$live}"); return "{$cs}_{$ts}_{$live}"; } diff --git a/CRM/Mailing/BAO/Mailing.php b/CRM/Mailing/BAO/Mailing.php index 277cf7df12..53b8dc5bf8 100644 --- a/CRM/Mailing/BAO/Mailing.php +++ b/CRM/Mailing/BAO/Mailing.php @@ -1562,6 +1562,8 @@ ORDER BY civicrm_email.is_bulkmail DESC $transaction->rollback(); return $mailing; } + // update mailings with hash values + CRM_Contact_BAO_Contact_Utils::generateChecksum($mailing->id, NULL, NULL, NULL, 'mailing', 16); $groupTableName = CRM_Contact_BAO_Group::getTableName(); $mailingTableName = CRM_Mailing_BAO_Mailing::getTableName(); @@ -1622,6 +1624,18 @@ ORDER BY civicrm_email.is_bulkmail DESC return $mailing; } + /** + * get hash value of the mailing + * + */ + public static function getMailingHash($id) { + $hash = NULL; + if (CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'hash_mailing_url')) { + $hash = CRM_Core_DAO::getFieldValue('CRM_Mailing_BAO_Mailing', $id, 'hash', 'id'); + } + return $hash; + } + /** * Generate a report. Fetch event count information, mailing data, and job * status. @@ -2564,14 +2578,21 @@ SELECT $mailing.id as mailing_id } } + $mailingKey = $form->_mailing_id; + if (!$isSMS) { + if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) { + $mailingKey = $hash; + } + } + if (!empty($report['mailing']['body_text'])) { - $url = CRM_Utils_System::url('civicrm/mailing/view', 'reset=1&text=1&id=' . $form->_mailing_id); + $url = CRM_Utils_System::url('civicrm/mailing/view', 'reset=1&text=1&id=' . $mailingKey); $form->assign('textViewURL', $url); } if (!$isSMS) { if (!empty($report['mailing']['body_html'])) { - $url = CRM_Utils_System::url('civicrm/mailing/view', 'reset=1&id=' . $form->_mailing_id); + $url = CRM_Utils_System::url('civicrm/mailing/view', 'reset=1&id=' . $mailingKey); $form->assign('htmlViewURL', $url); } } @@ -2757,7 +2778,7 @@ AND m.id = %1 CRM_Core_Action::VIEW => array( 'name' => ts('View'), 'url' => 'civicrm/mailing/view', - 'qs' => "reset=1&id=%%mid%%", + 'qs' => "reset=1&id=%%mkey%%", 'title' => ts('View Mailing'), 'class' => 'crm-popup', ), @@ -2769,12 +2790,18 @@ AND m.id = %1 ) ); + $mailingKey = $values['mailing_id']; + if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) { + $mailingKey = $hash; + } + $contactMailings[$mailingId]['links'] = CRM_Core_Action::formLink( $actionLinks, null, array( 'mid' => $values['mailing_id'], 'cid' => $params['contact_id'], + 'mkey' => $mailingKey, ), ts('more'), FALSE, @@ -2821,5 +2848,4 @@ AND m.id = %1 $params['version'] = 3; return civicrm_api('MailingContact', 'getcount', $params); } -} - +} \ No newline at end of file diff --git a/CRM/Mailing/Form/Approve.php b/CRM/Mailing/Form/Approve.php index 5b8d789060..0ffb8bc041 100644 --- a/CRM/Mailing/Form/Approve.php +++ b/CRM/Mailing/Form/Approve.php @@ -146,7 +146,13 @@ class CRM_Mailing_Form_Approve extends CRM_Core_Form { $this->_mailingID, 'subject' ); - $preview['viewURL'] = CRM_Utils_System::url('civicrm/mailing/view', "reset=1&id={$this->_mailingID}"); + + $mailingKey = $this->_mailingID; + if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) { + $mailingKey = $hash; + } + + $preview['viewURL'] = CRM_Utils_System::url('civicrm/mailing/view', "reset=1&id={$mailingKey}"); $preview['type'] = $this->_mailing->body_html ? 'html' : 'text'; $preview['attachment'] = CRM_Core_BAO_File::attachmentInfo('civicrm_mailing', $this->_mailingID); diff --git a/CRM/Mailing/Form/Schedule.php b/CRM/Mailing/Form/Schedule.php index 40de44860a..3dcd9055b6 100644 --- a/CRM/Mailing/Form/Schedule.php +++ b/CRM/Mailing/Form/Schedule.php @@ -168,7 +168,13 @@ class CRM_Mailing_Form_Schedule extends CRM_Core_Form { $this->_mailingID, 'subject' ); - $preview['viewURL'] = CRM_Utils_System::url('civicrm/mailing/view', "reset=1&id={$this->_mailingID}"); + + $mailingKey = $this->_mailingID; + if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) { + $mailingKey = $hash; + } + + $preview['viewURL'] = CRM_Utils_System::url('civicrm/mailing/view', "reset=1&id={$mailingKey}"); $preview['attachment'] = CRM_Core_BAO_File::attachmentInfo('civicrm_mailing', $this->_mailingID); diff --git a/CRM/Mailing/Page/View.php b/CRM/Mailing/Page/View.php index ea0198dad3..6f37e8415b 100644 --- a/CRM/Mailing/Page/View.php +++ b/CRM/Mailing/Page/View.php @@ -75,13 +75,13 @@ class CRM_Mailing_Page_View extends CRM_Core_Page { * * @return void */ - function run($id = NULL, $contactID = NULL, $print = TRUE) { + function run($id = NULL, $contactID = NULL, $print = TRUE, $allowID = FALSE) { if (is_numeric($id)) { $this->_mailingID = $id; } else { $print = TRUE; - $this->_mailingID = CRM_Utils_Request::retrieve('id', 'Integer', CRM_Core_DAO::$_nullObject, TRUE); + $this->_mailingID = CRM_Utils_Request::retrieve('id', 'String', CRM_Core_DAO::$_nullObject, TRUE); } // # CRM-7651 @@ -96,8 +96,30 @@ class CRM_Mailing_Page_View extends CRM_Core_Page { $this->_contactID = $session->get('userID'); } - $this->_mailing = new CRM_Mailing_BAO_Mailing(); - $this->_mailing->id = $this->_mailingID; + // mailing key check + if (CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'hash_mailing_url')) { + $this->_mailing = new CRM_Mailing_BAO_Mailing(); + + if (!is_numeric($this->_mailingID)) { + $this->_mailing->hash = $this->_mailingID; + } + elseif (is_numeric($this->_mailingID)) { + $this->_mailing->id = $this->_mailingID; + // if mailing is present and associated hash is present + // while 'hash' is not been used for mailing view : throw 'permissionDenied' + if ($this->_mailing->find() && + CRM_Core_DAO::getFieldValue('CRM_Mailing_BAO_Mailing', $this->_mailingID, 'hash', 'id') + !$allowID + ) { + CRM_Utils_System::permissionDenied(); + return; + } + } + } + else { + $this->_mailing = new CRM_Mailing_BAO_Mailing(); + $this->_mailing->id = $this->_mailingID; + } if (!$this->_mailing->find(TRUE) || !$this->checkPermission() diff --git a/CRM/Upgrade/Incremental/sql/4.5.alpha1.mysql.tpl b/CRM/Upgrade/Incremental/sql/4.5.alpha1.mysql.tpl index df4dd69386..32d3acab3c 100644 --- a/CRM/Upgrade/Incremental/sql/4.5.alpha1.mysql.tpl +++ b/CRM/Upgrade/Incremental/sql/4.5.alpha1.mysql.tpl @@ -267,4 +267,9 @@ VALUES -- CRM-14435 ALTER TABLE `civicrm_mail_settings` - ADD CONSTRAINT `FK_civicrm_mail_settings_domain_id` FOREIGN KEY (`domain_id`) REFERENCES `civicrm_domain` (`id`) ON DELETE SET NULL; \ No newline at end of file + ADD CONSTRAINT `FK_civicrm_mail_settings_domain_id` FOREIGN KEY (`domain_id`) REFERENCES `civicrm_domain` (`id`) ON DELETE SET NULL; + +-- CRM-14436 +ALTER TABLE `civicrm_mailing` + ADD COLUMN `hash` varchar(16) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Key for validating requests related to this mailing.', + ADD INDEX `index_hash` (`hash`); \ No newline at end of file diff --git a/CRM/Utils/Token.php b/CRM/Utils/Token.php index 18c21bf99d..60961b910e 100644 --- a/CRM/Utils/Token.php +++ b/CRM/Utils/Token.php @@ -432,8 +432,12 @@ class CRM_Utils_Token { break; case 'viewUrl': + $mailingKey = $mailing->id; + if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) { + $mailingKey = $hash; + } $value = CRM_Utils_System::url('civicrm/mailing/view', - "reset=1&id={$mailing->id}", + "reset=1&id={$mailingKey}", TRUE, NULL, FALSE, TRUE ); break; @@ -454,7 +458,7 @@ class CRM_Utils_Token { case 'html': $page = new CRM_Mailing_Page_View(); - $value = $page->run($mailing->id, NULL, FALSE); + $value = $page->run($mailing->id, NULL, FALSE, TRUE); break; case 'approvalStatus': diff --git a/settings/Mailing.setting.php b/settings/Mailing.setting.php index 81d6760fb5..f9101468a7 100644 --- a/settings/Mailing.setting.php +++ b/settings/Mailing.setting.php @@ -149,7 +149,7 @@ return array( 'is_contact' => 0, 'description' => 'Don\'t check for presence of mandatory tokens (domain address; unsubscribe/opt-out) before sending mailings. WARNING: Mandatory tokens are a safe-guard which facilitate compliance with the US CAN-SPAM Act. They should only be disabled if your organization adopts other mechanisms for compliance or if your organization is not subject to CAN-SPAM.', 'help_text' => null, - ), + ), 'dedupe_email_default' => array( 'group_name' => 'Mailing Preferences', 'group' => 'mailing', @@ -164,4 +164,18 @@ return array( 'description' => 'Set the "dedupe e-mail" option when sending a new mailing to "true" by default.', 'help_text' => null, ), -); + 'hash_mailing_url' => array( + 'group_name' => 'Mailing Preferences', + 'group' => 'mailing', + 'name' => 'hash_mailing_url', + 'type' => 'Integer', + 'html_type' => 'checkbox', + 'default' => 0, + 'add' => '4.5', + 'title' => 'Hashed Mailing URL\'s', + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => 'If enabled, a randomized hash key will be used to reference the mailing URL in the mailing.viewUrl token, instead of the mailing ID', + 'help_text' => null, + ), +); \ No newline at end of file diff --git a/xml/schema/Mailing/Mailing.xml b/xml/schema/Mailing/Mailing.xml index 81fb8d2e22..b926f1ea06 100644 --- a/xml/schema/Mailing/Mailing.xml +++ b/xml/schema/Mailing/Mailing.xml @@ -355,4 +355,17 @@ SET NULL 4.2 - + + hash + Mailing Hash + varchar + 16 + Key for validating requests related to this mailing. + 4.5 + + + index_hash + hash + 4.5 + + \ No newline at end of file -- 2.25.1