[TikiWiki-commits] [Git][tikiwiki/tiki][24.x] [BP][FIX] Fix Unsubscribe get request with no hash
"Elifeleti Mukisa Dan \(@Danelif\) via TikiWiki-cvs" <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <6a11af02ebc97_384c8f61491b@gitlab-sidekiq-low-urgency-cpu-bound-v2-d7f87744c-j9hf9.mail> |
Elifeleti Mukisa Dan pushed to branch 24.x at Tiki Wiki CMS Groupware / Tiki Commits: 4fe85ca0 by Elifeleti Mukisa Dan at 2026-05-23T13:38:12+00:00 [BP][FIX] Fix Unsubscribe get request with no hash --- * [BP][FIX] Fix Unsubscribe get request with no hash --- * [BP][FIX] Fix Unsubscribe get request with no hash --- * [BP][FIX] Fix Unsubscribe get request with no hash --- * [FIX] Fix Unsubscribe get request with no hash --- * [FIX] Fix Unsubscribe get request with no hash (cherry picked from commit a713cc9ed70513e0b41345032c99dc21820f8399) a713cc9e [FIX] Fix Unsubscribe get request with no hash Co-authored-by: Danelif <[email protected]> See merge request tikiwiki/tiki!10300 (cherry picked from commit 4e52055842453481e06daf310baf98779f3ffca8) dace1151 [FIX] Fix Unsubscribe get request with no hash Co-authored-by: Elifeleti Mukisa Dan <[email protected]> See merge request tikiwiki/tiki!10320 See merge request tikiwiki/tiki!10330 See merge request tikiwiki/tiki!10344 See merge request tikiwiki/tiki!10355 - - - - - 12 changed files: - lib/comments/commentslib.php - lib/notifications/notificationemaillib.php - + lib/smarty_tiki/block.mailurl.php - lib/tikilib.php - templates/mail/user_watch_article_post.tpl - templates/mail/user_watch_blog_post.tpl - templates/mail/user_watch_comment.tpl - templates/mail/user_watch_map_changed.tpl - templates/mail/user_watch_wiki_new_attach.tpl - templates/mail/user_watch_wiki_page_changed.tpl - templates/mail/user_watch_wiki_page_renamed.tpl - tiki-user_watches.php Changes: ===================================== lib/comments/commentslib.php ===================================== @@ -618,27 +618,57 @@ class Comments extends TikiLib if ($part['disposition'] == 'attachment') { if (! empty($part['d_parameters']['filename'])) { $part_name = $part['d_parameters']['filename']; - } elseif (preg_match( - '/filename=([^;]*)/', $part['d_parameters']['atend'], $mm - ) + } elseif ( + preg_match( + '/filename=([^;]*)/', + $part['d_parameters']['atend'], + $mm + ) ) { // not sure what this is but it seems to have the filename in it $part_name = $mm[1]; } else { $part_name = "Unnamed File"; } $this->add_thread_attachment( - $forum_info, $threadId, $errors, $part_name, $part['type'], strlen($part['body']), 1, '', '', $part['body'] + $forum_info, + $threadId, + $errors, + $part_name, + $part['type'], + strlen($part['body']), + 1, + '', + '', + $part['body'] ); } elseif ($part['disposition'] == 'inline') { if (! empty($part['parts'])) { foreach ($part['parts'] as $p) { $this->add_thread_attachment( - $forum_info, $threadId, $errors, '-', $p['type'], strlen($p['body']), 1, '', '', $p['body'] + $forum_info, + $threadId, + $errors, + '-', + $p['type'], + strlen($p['body']), + 1, + '', + '', + $p['body'] ); } } elseif (! empty($part['body'])) { $this->add_thread_attachment( - $forum_info, $threadId, $errors, '-', $part['type'], strlen($part['body']), 1, '', '', $part['body'] + $forum_info, + $threadId, + $errors, + '-', + $part['type'], + strlen($part['body']), + 1, + '', + '', + $part['body'] ); } } @@ -658,10 +688,10 @@ class Comments extends TikiLib $body, $userName, $title, - $message_id, - $in_reply_to, $threadId, - $parentId + $parentId, + $message_id, + $in_reply_to ); } $pop3->deleteMsg($i); @@ -872,10 +902,10 @@ class Comments extends TikiLib $info['data'], $info['user'], $info['title'], - $message_id, - $info['in_reply_to'], $threadId, - isset($info['parentId']) ? $info['parentId'] : 0 + isset($info['parentId']) ? $info['parentId'] : 0, + $message_id, + $info['in_reply_to'] ); if ($info['email']) { @@ -3926,10 +3956,10 @@ class Comments extends TikiLib $params['comments_data'], $user, isset($parent_comment_info['title']) ? $parent_comment_info['title'] : $params['comments_title'], - $message_id, - $in_reply_to, ! empty($params['comments_threadId']) ? $params['comments_threadId'] : 0, isset($params['comments_parentId']) ? $params['comments_parentId'] : 0, + $message_id, + $in_reply_to, isset($params['contributions']) ? $params['contributions'] : '', $qId ); @@ -3974,10 +4004,10 @@ class Comments extends TikiLib $params['comments_data'], $user, $params['comments_title'], - $message_id, - $in_reply_to, $threadId, isset($params['comments_parentId']) ? $params['comments_parentId'] : 0, + $message_id, + $in_reply_to, isset($params['contributions']) ? $params['contributions'] : '' ); // Set watch if requested ===================================== lib/notifications/notificationemaillib.php ===================================== @@ -23,10 +23,10 @@ function sendForumEmailNotification( $data, $author, $topicName, - $messageId = '', - $inReplyTo = '', $threadId, $parentId, + $messageId = '', + $inReplyTo = '', $contributions = '', $queueId = 0 ) { @@ -413,7 +413,12 @@ function sendWikiEmailNotification( if (empty($not['email'])) { continue; } - $smarty->assign('watchId', $not['watchId']); + $smarty->assign('watchId', $not['watchId'] ?? ''); + $watchUnsubscribeHash = ''; + if (! empty($not['watchId']) && ! empty($not['user'])) { + $watchUnsubscribeHash = $tikilib->generateWatchUnsubscribeHash($not['watchId'], $not['user']); + } + $smarty->assign('watchUnsubscribeHash', $watchUnsubscribeHash); $mail_subject = $smarty->fetchLang($not['language'], "mail/user_watch_wiki_page_changed_subject.tpl"); $mail_data = $smarty->fetchLang($not['language'], "mail/user_watch_wiki_page_changed.tpl"); @@ -472,7 +477,12 @@ function sendEmailNotification($watches, $dummy, $subjectTpl, $subjectParam, $tx $mail->setHeader($headerName, $headerValue); } - $smarty->assign('watchId', $watch['watchId']); + $smarty->assign('watchId', $watch['watchId'] ?? null); + $watchUnsubscribeHash = ''; + if (! empty($watch['watchId']) && ! empty($watch['user'])) { + $watchUnsubscribeHash = $tikilib->generateWatchUnsubscribeHash($watch['watchId'], $watch['user']); + } + $smarty->assign('watchUnsubscribeHash', $watchUnsubscribeHash); if ($watch['user']) { $mail->setUser($watch['user']); } @@ -708,7 +718,6 @@ function sendCategoryEmailNotification($values) $mail_subject = $smarty->fetchLang($not['language'], "mail/user_watch_category_removed_subject.tpl"); $mail_data = $smarty->fetchLang($not['language'], "mail/user_watch_category_removed.tpl"); } elseif ($action == 'category updated') { - $smarty->assign('oldCategoryName', $oldCategoryName); $smarty->assign('oldCategoryPath', $oldCategoryPath); $smarty->assign('oldDescription', $oldDescription); @@ -718,7 +727,6 @@ function sendCategoryEmailNotification($values) $mail_subject = $smarty->fetchLang($not['language'], "mail/user_watch_category_updated_subject.tpl"); $mail_data = $smarty->fetchLang($not['language'], "mail/user_watch_category_updated.tpl"); } elseif ($action == 'object entered category') { - $smarty->assign('objectName', $objectName); $smarty->assign('objectType', $objectType); $smarty->assign('objectUrl', $objectUrl); @@ -726,7 +734,6 @@ function sendCategoryEmailNotification($values) $mail_subject = $smarty->fetchLang($not['language'], "mail/user_watch_object_entered_category_subject.tpl"); $mail_data = $smarty->fetchLang($not['language'], "mail/user_watch_object_entered_category.tpl"); } elseif ($action == 'object leaved category') { - $smarty->assign('objectName', $objectName); $smarty->assign('objectType', $objectType); $smarty->assign('objectUrl', $objectUrl); ===================================== lib/smarty_tiki/block.mailurl.php ===================================== @@ -0,0 +1,29 @@ +<?php + +// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project +// +// All Rights Reserved. See copyright.txt for details and a complete list of authors. +// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. +// $Id$ + +/** + * Smarty plugin + * @package Smarty + * @subpackage plugins + * + * smarty_block_mailurl creates a full URL for use in mail notifications + * by prepending the base URL (mail_machine_raw) to the content. + * + * Usage: + * {mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} + */ +function smarty_block_mailurl($params, $content, $smarty, &$repeat) +{ + if ($repeat || $content === null) { + return; + } + + $mail_machine_raw = $smarty->getTemplateVars('mail_machine_raw'); + + return rtrim((string) $mail_machine_raw, '/') . '/' . ltrim($content, '/'); +} ===================================== lib/tikilib.php ===================================== @@ -851,6 +851,39 @@ class TikiLib extends TikiDb_Bridge return $this->table('tiki_user_watches')->fetchOne('user', ['watchId' => $id]); } + + /** + * Generate an HMAC hash for a watch unsubscribe link. + * + * The hash binds the watchId to its owner so that only someone who + * received the original email notification can use the link. + * + * @param int $watchId + * @param string $user Owner of the watch + * @return string Hex-encoded HMAC-SHA256 + */ + public function generateWatchUnsubscribeHash($watchId, $user) + { + $key = $this->get_site_hash(); + return hash_hmac('sha256', $watchId . ':' . $user, $key); + } + + /** + * Verify an HMAC hash for a watch unsubscribe link. + * + * @param int $watchId + * @param string $hash Hash received from the request + * @return bool True if the hash is valid for this watchId + */ + public function verifyWatchUnsubscribeHash($watchId, $hash) + { + $owner = $this->get_user_notification($watchId); + if (! $owner) { + return false; + } + $expected = $this->generateWatchUnsubscribeHash($watchId, $owner); + return hash_equals($expected, $hash); + } /*shared*/ /** * @param $id ===================================== templates/mail/user_watch_article_post.tpl ===================================== @@ -16,7 +16,7 @@ {tr}Expiration Date:{/tr} {$mail_current_expiration_date|tiki_short_datetime:"":"n"} {if !empty($watchId)}{tr}If you don't want to receive these notifications follow this link:{/tr} -{$mail_machine_raw}/tiki-user_watches.php?id={$watchId}{/if} +{mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl}{/if} {if isset($mail_old_data)} ===================================== templates/mail/user_watch_blog_post.tpl ===================================== @@ -7,4 +7,4 @@ {$mail_machine_raw}/{$mail_postid|sefurl:blogpost} {tr}If you don't want to receive these notifications follow this link:{/tr} -{$mail_machine_raw}/tiki-user_watches.php?id={$watchId} +{mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} ===================================== templates/mail/user_watch_comment.tpl ===================================== @@ -27,6 +27,6 @@ {if $watchId} {tr}If you don't want to receive these notifications follow this link:{/tr} -{$mail_machine_raw}/tiki-user_watches.php?id={$watchId} +{mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} {/if} ===================================== templates/mail/user_watch_map_changed.tpl ===================================== @@ -7,4 +7,4 @@ {$mail_machine}?mapfile={$mail_page} {tr}If you don't want to receive these notifications follow this link:{/tr} -{$mail_machine_raw}/tiki-user_watches.php?id={$watchId} +{mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} ===================================== templates/mail/user_watch_wiki_new_attach.tpl ===================================== @@ -9,4 +9,4 @@ {$mail_machine_raw}/{$mail_page|sefurl} {tr}If you don't want to receive these notifications follow this link:{/tr} -{$mail_machine_raw}/tiki-user_watches.php?id={$watchId} +{mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} ===================================== templates/mail/user_watch_wiki_page_changed.tpl ===================================== @@ -22,7 +22,7 @@ {if $watchId} {tr}If you don't want to receive these notifications follow this link:{/tr} - {$mail_machine_raw}/tiki-user_watches.php?id={$watchId} + {mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} {/if} *********************************************************** ===================================== templates/mail/user_watch_wiki_page_renamed.tpl ===================================== @@ -6,4 +6,4 @@ {tr}New name:{/tr} {$mail_newname} {tr}If you don't want to receive these notifications follow this link:{/tr} -{$mail_machine_raw}/tiki-user_watches.php?id={$watchId} +{mailurl}tiki-user_watches.php?id={$watchId}&hash={$watchUnsubscribeHash}{/mailurl} ===================================== tiki-user_watches.php ===================================== @@ -55,14 +55,20 @@ if (isset($_REQUEST['categwatch'])) { } } // request from unsubscribe email link, like in templates/mail/user_watch_map_changed.tpl -// TODO would be better to provide a token with the unsubscribe link that could be matched to the database -// TODO so that the user doesn't have to log in to unsubscribe - similar to unsubscribe in tiki-newsletter.php +// The link includes an HMAC hash so that only the recipient of the email can unsubscribe. if (isset($_REQUEST['id'])) { - if ($tiki_p_admin_notifications != 'y' && $user != $tikilib->get_user_notification($_REQUEST['id'])) { + $watchId = $_REQUEST['id']; + $hash = isset($_REQUEST['hash']) ? $_REQUEST['hash'] : ''; + + if (empty($hash) || ! $tikilib->verifyWatchUnsubscribeHash($watchId, $hash)) { + Feedback::errorPage(['mes' => tr('Invalid or missing unsubscribe token'), 'errortype' => 403]); + } + + if ($tiki_p_admin_notifications != 'y' && $user != $tikilib->get_user_notification($watchId)) { Feedback::errorPage(['mes' => tr('Permission denied'), 'errortype' => 401]); } if ($access->checkCsrf()) { - $result = $tikilib->remove_user_watch_by_id($_REQUEST['id']); + $result = $tikilib->remove_user_watch_by_id($watchId); } if ($result && $result->numRows()) { Feedback::success(tr('Unsubscribed from user watch email notification')); View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/4fe85ca0ad8bb3a2ef59926b465cb00c59770406 -- View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/4fe85ca0ad8bb3a2ef59926b465cb00c59770406 You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help _______________________________________________ TikiWiki-cvs mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs