From e4609c18efceebb1e021d814f53061ada7f6489a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov <noreply@fakecake.org> Date: Wed, 17 Feb 2021 21:44:21 +0300 Subject: [PATCH] * add (disabled) shortcut syntax for plugin methods * add controls shortcut for pluginhandler tags * add similar shortcut for frontend * allow plugins to selectively exclude their methods from CSRF checking --- backend.php | 11 +++++++++++ classes/plugin.php | 4 ++++ classes/pluginhandler.php | 2 +- classes/pluginhost.php | 13 ++++++++++++- include/controls.php | 12 +++++++++++- js/App.js | 3 +++ plugins/af_proxy_http/init.php | 4 +--- plugins/af_psql_trgm/init.php | 4 +--- plugins/af_readability/init.js | 2 +- plugins/af_readability/init.php | 6 ++---- plugins/af_redditimgur/init.php | 9 +++++---- plugins/mail/init.php | 12 ++++-------- plugins/mail/mail.js | 2 +- plugins/mailto/init.js | 2 +- plugins/note/init.php | 4 +--- plugins/note/note.js | 2 +- plugins/nsfw/init.php | 4 +--- plugins/share/share.js | 8 +++----- plugins/share/share_prefs.js | 2 +- 19 files changed, 65 insertions(+), 41 deletions(-) diff --git a/backend.php b/backend.php index 9ecc22914..e64c6561f 100644 --- a/backend.php +++ b/backend.php @@ -88,6 +88,17 @@ 5 => __("Power User"), 10 => __("Administrator")); + // shortcut syntax for plugin methods (?op=plugin--pmethod&...params) + /* if (strpos($op, PluginHost::PUBLIC_METHOD_DELIMITER) !== false) { + list ($plugin, $pmethod) = explode(PluginHost::PUBLIC_METHOD_DELIMITER, $op, 2); + + // TODO: better implementation that won't modify $_REQUEST + $_REQUEST["plugin"] = $plugin; + $method = $pmethod; + $op = "pluginhandler"; + } */ + + // TODO: figure out if is this still needed $op = str_replace("-", "_", $op); $override = PluginHost::getInstance()->lookup_handler($op, $method); diff --git a/classes/plugin.php b/classes/plugin.php index 2416418cd..6c572467a 100644 --- a/classes/plugin.php +++ b/classes/plugin.php @@ -54,4 +54,8 @@ abstract class Plugin { return vsprintf($this->__($msgid), $args); } + + function csrf_ignore($method) { + return false; + } } diff --git a/classes/pluginhandler.php b/classes/pluginhandler.php index a0e60b4e6..608f80dcb 100644 --- a/classes/pluginhandler.php +++ b/classes/pluginhandler.php @@ -11,7 +11,7 @@ class PluginHandler extends Handler_Protected { if ($plugin) { if (method_exists($plugin, $method)) { - if (validate_csrf($csrf_token)) { + if (validate_csrf($csrf_token) || $plugin->csrf_ignore($method)) { $plugin->$method(); } else { user_error("Rejected ${plugin_name}->${method}(): invalid CSRF token.", E_USER_WARNING); diff --git a/classes/pluginhost.php b/classes/pluginhost.php index 097bf987c..065fa99c4 100755 --- a/classes/pluginhost.php +++ b/classes/pluginhost.php @@ -611,6 +611,17 @@ class PluginHost { $params)); } + // shortcut syntax (disabled for now) + /* function get_method_url(Plugin $sender, string $method, $params) { + return get_self_url_prefix() . "/backend.php?" . + http_build_query( + array_merge( + [ + "op" => strtolower(get_class($sender) . self::PUBLIC_METHOD_DELIMITER . $method), + ], + $params)); + } */ + // WARNING: endpoint in public.php, exposed to unauthenticated users function get_public_method_url(Plugin $sender, string $method, $params) { if ($sender->is_public_method($method)) { @@ -618,7 +629,7 @@ class PluginHost { http_build_query( array_merge( [ - "op" => strtolower(get_class($sender) . PluginHost::PUBLIC_METHOD_DELIMITER . $method), + "op" => strtolower(get_class($sender) . self::PUBLIC_METHOD_DELIMITER . $method), ], $params)); } else { diff --git a/include/controls.php b/include/controls.php index 4c60d94f3..d8506877b 100755 --- a/include/controls.php +++ b/include/controls.php @@ -11,6 +11,17 @@ return $rv; } + // shortcut syntax (disabled) + /* function pluginhandler_tags(\Plugin $plugin, string $method) { + return hidden_tag("op", strtolower(get_class($plugin) . \PluginHost::PUBLIC_METHOD_DELIMITER . $method)); + } */ + + function pluginhandler_tags(\Plugin $plugin, string $method) { + return hidden_tag("op", "pluginhandler") . + hidden_tag("plugin", strtolower(get_class($plugin))) . + hidden_tag("method", $method); + } + function button_tag(string $value, string $type, array $attributes = []) { return "<button dojoType=\"dijit.form.Button\" ".attributes_to_string($attributes)." type=\"$type\">".htmlspecialchars($value)."</button>"; } @@ -155,4 +166,3 @@ return $ret; } - diff --git a/js/App.js b/js/App.js index 9d8f6c275..aeca688b7 100644 --- a/js/App.js +++ b/js/App.js @@ -101,6 +101,9 @@ const App = { return dijit.getEnclosingWidget(elem.closest('.dijitDialog')); }, + getPhArgs(plugin, method, args = {}) { + return {...{op: "pluginhandler", plugin: plugin, method: method}, ...args}; + }, label_to_feed_id: function(label) { return this.LABEL_BASE_INDEX - 1 - Math.abs(label); }, diff --git a/plugins/af_proxy_http/init.php b/plugins/af_proxy_http/init.php index 5804e450f..d6cee5fcd 100644 --- a/plugins/af_proxy_http/init.php +++ b/plugins/af_proxy_http/init.php @@ -229,9 +229,7 @@ class Af_Proxy_Http extends Plugin { } </script>"; - print \Controls\hidden_tag("op", "pluginhandler"); - print \Controls\hidden_tag("method", "save"); - print \Controls\hidden_tag("plugin", "af_proxy_http"); + print \Controls\pluginhandler_tags($this, "save"); $proxy_all = sql_bool_to_bool($this->host->get($this, "proxy_all")); print \Controls\checkbox_tag("proxy_all", $proxy_all); diff --git a/plugins/af_psql_trgm/init.php b/plugins/af_psql_trgm/init.php index 1d83ce5e0..bfbbdf49c 100644 --- a/plugins/af_psql_trgm/init.php +++ b/plugins/af_psql_trgm/init.php @@ -157,9 +157,7 @@ class Af_Psql_Trgm extends Plugin { } </script>"; - print \Controls\hidden_tag("op", "pluginhandler"); - print \Controls\hidden_tag("method", "save"); - print \Controls\hidden_tag("plugin", "af_psql_trgm"); + print \Controls\pluginhandler_tags($this, "save"); print "<h2>" . __("Global settings") . "</h2>"; diff --git a/plugins/af_readability/init.js b/plugins/af_readability/init.js index 3155475cc..ff2d94e8b 100644 --- a/plugins/af_readability/init.js +++ b/plugins/af_readability/init.js @@ -16,7 +16,7 @@ Plugins.Af_Readability = { Notify.progress("Loading, please wait..."); - xhrJson("backend.php",{ op: "pluginhandler", plugin: "af_readability", method: "embed", param: id }, (reply) => { + xhrJson("backend.php", App.getPhArgs("af_readability", "embed", {id: id}), (reply) => { if (content && reply.content) { content.setAttribute(self.orig_attr_name, content.innerHTML); diff --git a/plugins/af_readability/init.php b/plugins/af_readability/init.php index aeef8cddc..43d064fc7 100755 --- a/plugins/af_readability/init.php +++ b/plugins/af_readability/init.php @@ -67,9 +67,7 @@ class Af_Readability extends Plugin { <form dojoType='dijit.form.Form'> - <?= \Controls\hidden_tag("op", "pluginhandler") ?> - <?= \Controls\hidden_tag("method", "save") ?> - <?= \Controls\hidden_tag("plugin", "af_readability") ?> + <?= \Controls\pluginhandler_tags($this, "save") ?> <script type='dojo/method' event='onSubmit' args='evt'> evt.preventDefault(); @@ -329,7 +327,7 @@ class Af_Readability extends Plugin { } function embed() { - $article_id = (int) $_REQUEST["param"]; + $article_id = (int) $_REQUEST["id"]; $sth = $this->pdo->prepare("SELECT link FROM ttrss_entries WHERE id = ?"); $sth->execute([$article_id]); diff --git a/plugins/af_redditimgur/init.php b/plugins/af_redditimgur/init.php index 63a23cd36..5066186db 100755 --- a/plugins/af_redditimgur/init.php +++ b/plugins/af_redditimgur/init.php @@ -41,9 +41,7 @@ class Af_RedditImgur extends Plugin { <form dojoType='dijit.form.Form'> - <?= \Controls\hidden_tag("op", "pluginhandler") ?> - <?= \Controls\hidden_tag("method", "save") ?> - <?= \Controls\hidden_tag("plugin", "af_redditimgur") ?> + <?= \Controls\pluginhandler_tags($this, "save") ?> <script type='dojo/method' event='onSubmit' args='evt'> evt.preventDefault(); @@ -633,6 +631,10 @@ class Af_RedditImgur extends Plugin { $entry->parentNode->insertBefore($img, $entry);*/ } + function csrf_ignore($method) { + return $method === "testurl"; + } + function testurl() { $url = clean($_POST["url"]); @@ -651,7 +653,6 @@ class Af_RedditImgur extends Plugin { <input type="hidden" name="op" value="pluginhandler"> <input type="hidden" name="method" value="testurl"> <input type="hidden" name="plugin" value="af_redditimgur"> - <input type="hidden" name="csrf_token" value="<?= $_SESSION["csrf_token"] ?>"> <fieldset> <label>URL:</label> <input name="url" size="100" value="<?= htmlspecialchars($url) ?>"></input> diff --git a/plugins/mail/init.php b/plugins/mail/init.php index bb576a4d9..4b62d1e64 100644 --- a/plugins/mail/init.php +++ b/plugins/mail/init.php @@ -45,9 +45,7 @@ class Mail extends Plugin { title="<i class='material-icons'>mail</i> <?= __('Mail plugin') ?>"> <form dojoType="dijit.form.Form"> - <?= \Controls\hidden_tag("op", "pluginhandler") ?> - <?= \Controls\hidden_tag("method", "save") ?> - <?= \Controls\hidden_tag("plugin", "mail") ?> + <?= \Controls\pluginhandler_tags($this, "save") ?> <script type="dojo/method" event="onSubmit" args="evt"> evt.preventDefault(); @@ -150,12 +148,10 @@ class Mail extends Plugin { <form dojoType='dijit.form.Form'> - <?= \Controls\hidden_tag("op", "pluginhandler") ?> - <?= \Controls\hidden_tag("plugin", "mail") ?> - <?= \Controls\hidden_tag("method", "sendEmail") ?> + <?= \Controls\pluginhandler_tags($this, "sendemail") ?> - <?= \Controls\hidden_tag("from_email", "$user_email") ?> - <?= \Controls\hidden_tag("from_name", "$user_name") ?> + <?= \Controls\hidden_tag("from_email", $user_email) ?> + <?= \Controls\hidden_tag("from_name", $user_name) ?> <script type='dojo/method' event='onSubmit' args='evt'> evt.preventDefault(); diff --git a/plugins/mail/mail.js b/plugins/mail/mail.js index 4cdf6999d..36b0baac5 100644 --- a/plugins/mail/mail.js +++ b/plugins/mail/mail.js @@ -38,7 +38,7 @@ Plugins.Mail = { const tmph = dojo.connect(dialog, 'onShow', function () { dojo.disconnect(tmph); - xhrPost("backend.php", {op: "pluginhandler", plugin: "mail", method: "emailArticle", ids: id}, (transport) => { + xhrPost("backend.php", App.getPhArgs("mail", "emailArticle", {ids: id}), (transport) => { dialog.attr('content', transport.responseText); }); }); diff --git a/plugins/mailto/init.js b/plugins/mailto/init.js index 56afa1cca..b5e68680b 100644 --- a/plugins/mailto/init.js +++ b/plugins/mailto/init.js @@ -21,7 +21,7 @@ Plugins.Mailto = { const tmph = dojo.connect(dialog, 'onShow', function () { dojo.disconnect(tmph); - xhrPost("backend.php", {op: "pluginhandler", plugin: "mailto", method: "emailArticle", ids: id}, (transport) => { + xhrPost("backend.php", App.getPhArgs("mailto", "emailArticle", {ids: id}), (transport) => { dialog.attr('content', transport.responseText); }); }); diff --git a/plugins/note/init.php b/plugins/note/init.php index 65e1f0eef..278cfe6c3 100644 --- a/plugins/note/init.php +++ b/plugins/note/init.php @@ -38,9 +38,7 @@ class Note extends Plugin { $note = $row['note']; print \Controls\hidden_tag("id", $id); - print \Controls\hidden_tag("op", "pluginhandler"); - print \Controls\hidden_tag("method", "setNote"); - print \Controls\hidden_tag("plugin", "note"); + print \Controls\pluginhandler_tags($this, "setnote"); ?> <textarea dojoType='dijit.form.SimpleTextarea' diff --git a/plugins/note/note.js b/plugins/note/note.js index 215058b21..bc6c48156 100644 --- a/plugins/note/note.js +++ b/plugins/note/note.js @@ -33,7 +33,7 @@ Plugins.Note = { const tmph = dojo.connect(dialog, 'onShow', function () { dojo.disconnect(tmph); - xhrPost("backend.php", {op: "pluginhandler", plugin: "note", method: "edit", id: id}, (transport) => { + xhrPost("backend.php", App.getPhArgs("note", "edit", {id: id}), (transport) => { dialog.attr('content', transport.responseText); }); }); diff --git a/plugins/nsfw/init.php b/plugins/nsfw/init.php index fecbc62af..0ee3aebc1 100644 --- a/plugins/nsfw/init.php +++ b/plugins/nsfw/init.php @@ -50,9 +50,7 @@ class NSFW extends Plugin { title="<i class='material-icons'>extension</i> <?= __("NSFW Plugin") ?>"> <form dojoType="dijit.form.Form"> - <?= \Controls\hidden_tag("op", "pluginhandler") ?> - <?= \Controls\hidden_tag("method", "save") ?> - <?= \Controls\hidden_tag("plugin", "nsfw") ?> + <?= \Controls\pluginhandler_tags($this, "save") ?> <script type="dojo/method" event="onSubmit" args="evt"> evt.preventDefault(); diff --git a/plugins/share/share.js b/plugins/share/share.js index 09fb145c9..46b62ca5b 100644 --- a/plugins/share/share.js +++ b/plugins/share/share.js @@ -10,9 +10,7 @@ Plugins.Share = { Notify.progress("Trying to change URL...", true); - const query = {op: "pluginhandler", plugin: "share", method: "newkey", id: id}; - - xhrJson("backend.php", query, (reply) => { + xhrJson("backend.php", App.getPhArgs("share", "newkey", {id: id}), (reply) => { if (reply) { const new_link = reply.link; const target = dialog.domNode.querySelector(".target-url"); @@ -45,7 +43,7 @@ Plugins.Share = { }, unshare: function () { if (confirm(__("Remove sharing for this article?"))) { - xhrPost("backend.php", {op: "pluginhandler", plugin: "share", method: "unshare", id: id}, (transport) => { + xhrPost("backend.php", App.getPhArgs("share", "unshare", {id: id}), (transport) => { Notify.info(transport.responseText); const icon = document.querySelector(".share-icon-" + id); @@ -64,7 +62,7 @@ Plugins.Share = { const tmph = dojo.connect(dialog, 'onShow', function () { dojo.disconnect(tmph); - xhrPost("backend.php", {op: "pluginhandler", plugin: "share", method: "shareDialog", id: id}, (transport) => { + xhrPost("backend.php", App.getPhArgs("share", "shareDialog", {id: id}), (transport) => { dialog.attr('content', transport.responseText) const icon = document.querySelector(".share-icon-" + id); diff --git a/plugins/share/share_prefs.js b/plugins/share/share_prefs.js index 29c9aeaf8..91f979daf 100644 --- a/plugins/share/share_prefs.js +++ b/plugins/share/share_prefs.js @@ -5,7 +5,7 @@ Plugins.Share = { if (confirm(__("This will invalidate all previously shared article URLs. Continue?"))) { Notify.progress("Clearing URLs..."); - xhrPost("backend.php", {op: "pluginhandler", plugin: "share", method: "clearArticleKeys"}, (transport) => { + xhrPost("backend.php", App.getPhArgs("share", "clearArticleKeys"), (transport) => { Notify.info(transport.responseText); }); } -- GitLab