diff options
author | Andrew Dolgov <[email protected]> | 2021-11-10 20:44:51 +0300 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2021-11-10 20:44:51 +0300 |
commit | 9e8d69739f21e5ac85977d57a2a6c961e318c26e (patch) | |
tree | 0fc52f7be644b5f86e236cc7cb8f4dc4351da8f9 | |
parent | 7a52560e4e3b0652d32645b60ae13e4904f606bc (diff) |
add two helper account access levels:
- read only - can't subscribe to more feeds, feed updates are skipped
- disabled - can't login
define used access levels as UserHelper constants and refactor code to
use them instead of hardcoded numbers
-rw-r--r-- | backend.php | 11 | ||||
-rwxr-xr-x | classes/feeds.php | 7 | ||||
-rw-r--r-- | classes/handler/administrative.php | 2 | ||||
-rwxr-xr-x | classes/pref/feeds.php | 12 | ||||
-rw-r--r-- | classes/pref/prefs.php | 16 | ||||
-rwxr-xr-x | classes/rpc.php | 7 | ||||
-rwxr-xr-x | classes/rssutils.php | 19 | ||||
-rw-r--r-- | classes/userhelper.php | 19 | ||||
-rw-r--r-- | include/sessions.php | 9 | ||||
-rw-r--r-- | js/App.js | 12 | ||||
-rw-r--r-- | js/CommonDialogs.js | 15 | ||||
-rw-r--r-- | js/PrefUsers.js | 2 | ||||
-rw-r--r-- | prefs.php | 2 |
13 files changed, 105 insertions, 28 deletions
diff --git a/backend.php b/backend.php index bd24416f6..cb7daadad 100644 --- a/backend.php +++ b/backend.php @@ -86,10 +86,13 @@ 1440 => __("Daily"), 10080 => __("Weekly")); - $access_level_names = array( - 0 => __("User"), - 5 => __("Power User"), - 10 => __("Administrator")); + $access_level_names = [ + UserHelper::ACCESS_LEVEL_DISABLED => __("Disabled"), + UserHelper::ACCESS_LEVEL_READONLY => __("Read Only"), + UserHelper::ACCESS_LEVEL_USER => __("User"), + UserHelper::ACCESS_LEVEL_POWERUSER => __("Power User"), + UserHelper::ACCESS_LEVEL_ADMIN => __("Administrator") + ]; // shortcut syntax for plugin methods (?op=plugin--pmethod&...params) /* if (strpos($op, PluginHost::PUBLIC_METHOD_DELIMITER) !== false) { diff --git a/classes/feeds.php b/classes/feeds.php index 987123a21..cd2633ffb 100755 --- a/classes/feeds.php +++ b/classes/feeds.php @@ -1027,10 +1027,17 @@ class Feeds extends Handler_Protected { * 5 - Couldn't download the URL content. * 6 - Content is an invalid XML. * 7 - Error while creating feed database entry. + * 8 - Permission denied (ACCESS_LEVEL_READONLY). */ static function _subscribe($url, $cat_id = 0, $auth_login = '', $auth_pass = '') : array { + $user = ORM::for_table("ttrss_users")->find_one($_SESSION['uid']); + + if ($user && $user->access_level == UserHelper::ACCESS_LEVEL_READONLY) { + return ["code" => 8]; + } + $pdo = Db::pdo(); $url = UrlHelper::validate($url); diff --git a/classes/handler/administrative.php b/classes/handler/administrative.php index 52dfed8b7..f2f5b36ba 100644 --- a/classes/handler/administrative.php +++ b/classes/handler/administrative.php @@ -2,7 +2,7 @@ class Handler_Administrative extends Handler_Protected { function before($method) { if (parent::before($method)) { - if (($_SESSION["access_level"] ?? 0) >= 10) { + if (($_SESSION["access_level"] ?? 0) >= UserHelper::ACCESS_LEVEL_ADMIN) { return true; } } diff --git a/classes/pref/feeds.php b/classes/pref/feeds.php index 95bbcd190..ac0874259 100755 --- a/classes/pref/feeds.php +++ b/classes/pref/feeds.php @@ -538,6 +538,8 @@ class Pref_Feeds extends Handler_Protected { $local_purge_intervals = [ T_nsprintf('%d day', '%d days', $purge_interval, $purge_interval) ]; } + $user = ORM::for_table("ttrss_users")->find_one($_SESSION["uid"]); + print json_encode([ "feed" => $row, "cats" => [ @@ -550,6 +552,9 @@ class Pref_Feeds extends Handler_Protected { "update" => $local_update_intervals, "purge" => $local_purge_intervals, ], + "user" => [ + "access_level" => $user->access_level + ], "lang" => [ "enabled" => Config::get(Config::DB_TYPE) == "pgsql", "default" => get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE), @@ -1207,6 +1212,13 @@ class Pref_Feeds extends Handler_Protected { $login = clean($_REQUEST['login']); $pass = clean($_REQUEST['pass']); + $user = ORM::for_table('ttrss_users')->find_one($_SESSION["uid"]); + + // TODO: we should return some kind of error code to frontend here + if ($user->access_level == UserHelper::ACCESS_LEVEL_READONLY) { + return false; + } + $csth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ? AND owner_uid = ?"); diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php index c47a99469..c45d6d6ea 100644 --- a/classes/pref/prefs.php +++ b/classes/pref/prefs.php @@ -813,7 +813,7 @@ class Pref_Prefs extends Handler_Protected { usort($rv, function($a, $b) { return strcmp($a["name"], $b["name"]); }); - print json_encode(['plugins' => $rv, 'is_admin' => $_SESSION['access_level'] >= 10]); + print json_encode(['plugins' => $rv, 'is_admin' => $_SESSION['access_level'] >= UserHelper::ACCESS_LEVEL_ADMIN]); } function index_plugins() { @@ -890,7 +890,7 @@ class Pref_Prefs extends Handler_Protected { <?= \Controls\button_tag(\Controls\icon("refresh"), "", ["title" => __("Reload"), "onclick" => "Helpers.Plugins.reload()"]) ?> - <?php if ($_SESSION["access_level"] >= 10) { ?> + <?php if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { ?> <?php if (Config::get(Config::CHECK_FOR_UPDATES) && Config::get(Config::CHECK_FOR_PLUGIN_UPDATES)) { ?> <button class='alt-warning' dojoType='dijit.form.Button' onclick="Helpers.Plugins.update()"> @@ -1152,7 +1152,7 @@ class Pref_Prefs extends Handler_Protected { } function uninstallPlugin() { - if ($_SESSION["access_level"] >= 10) { + if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { $plugin_name = basename(clean($_REQUEST['plugin'])); $status = 0; @@ -1167,7 +1167,7 @@ class Pref_Prefs extends Handler_Protected { } function installPlugin() { - if ($_SESSION["access_level"] >= 10 && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) { + if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) { $plugin_name = basename(clean($_REQUEST['plugin'])); $all_plugins = $this->_get_available_plugins(); $plugin_dir = dirname(dirname(__DIR__)) . "/plugins.local"; @@ -1252,18 +1252,18 @@ class Pref_Prefs extends Handler_Protected { } private function _get_available_plugins() { - if ($_SESSION["access_level"] >= 10 && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) { + if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::ENABLE_PLUGIN_INSTALLER)) { return json_decode(UrlHelper::fetch(['url' => 'https://tt-rss.org/plugins.json']), true); } } function getAvailablePlugins() { - if ($_SESSION["access_level"] >= 10) { + if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { print json_encode($this->_get_available_plugins()); } } function checkForPluginUpdates() { - if ($_SESSION["access_level"] >= 10 && Config::get(Config::CHECK_FOR_UPDATES) && Config::get(Config::CHECK_FOR_PLUGIN_UPDATES)) { + if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && Config::get(Config::CHECK_FOR_UPDATES) && Config::get(Config::CHECK_FOR_PLUGIN_UPDATES)) { $plugin_name = $_REQUEST["name"] ?? ""; $root_dir = dirname(dirname(__DIR__)); # we're in classes/pref/ @@ -1279,7 +1279,7 @@ class Pref_Prefs extends Handler_Protected { } function updateLocalPlugins() { - if ($_SESSION["access_level"] >= 10) { + if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { $plugins = explode(",", $_REQUEST["plugins"] ?? ""); # we're in classes/pref/ diff --git a/classes/rpc.php b/classes/rpc.php index b6c4a5fc9..0432ed2d3 100755 --- a/classes/rpc.php +++ b/classes/rpc.php @@ -299,7 +299,8 @@ class RPC extends Handler_Protected { ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON (p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL') WHERE - f.owner_uid = u.id + f.owner_uid = u.id AND + u.access_level NOT IN (".sprintf("%d, %d", UserHelper::ACCESS_LEVEL_DISABLED, UserHelper::ACCESS_LEVEL_READONLY).") $owner_check_qpart $update_limit_qpart $updstart_thresh_qpart @@ -403,7 +404,7 @@ class RPC extends Handler_Protected { $git_timestamp = $version["timestamp"] ?? false; $git_commit = $version["commit"] ?? false; - if (Config::get(Config::CHECK_FOR_UPDATES) && $_SESSION["access_level"] >= 10 && $git_timestamp) { + if (Config::get(Config::CHECK_FOR_UPDATES) && $_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN && $git_timestamp) { $content = @UrlHelper::fetch(["url" => "https://tt-rss.org/version.json"]); if ($content) { @@ -510,7 +511,7 @@ class RPC extends Handler_Protected { $data['cdm_expanded'] = get_pref(Prefs::CDM_EXPANDED); $data["labels"] = Labels::get_all($_SESSION["uid"]); - if (Config::get(Config::LOG_DESTINATION) == 'sql' && $_SESSION['access_level'] >= 10) { + if (Config::get(Config::LOG_DESTINATION) == 'sql' && $_SESSION['access_level'] >= UserHelper::ACCESS_LEVEL_ADMIN) { if (Config::get(Config::DB_TYPE) == 'pgsql') { $log_interval = "created_at > NOW() - interval '1 hour'"; } else { diff --git a/classes/rssutils.php b/classes/rssutils.php index 87e52ba42..927a6c251 100755 --- a/classes/rssutils.php +++ b/classes/rssutils.php @@ -123,7 +123,8 @@ class RSSUtils { ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON (p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL') WHERE - f.owner_uid = u.id + f.owner_uid = u.id AND + u.access_level NOT IN (".sprintf("%d, %d", UserHelper::ACCESS_LEVEL_DISABLED, UserHelper::ACCESS_LEVEL_READONLY).") $login_thresh_qpart $update_limit_qpart $updstart_thresh_qpart @@ -163,7 +164,8 @@ class RSSUtils { FROM ttrss_feeds f, ttrss_users u LEFT JOIN ttrss_user_prefs2 p ON (p.owner_uid = u.id AND profile IS NULL AND pref_name = 'DEFAULT_UPDATE_INTERVAL') WHERE - f.owner_uid = u.id + f.owner_uid = u.id AND + u.access_level NOT IN (".sprintf("%d, %d", UserHelper::ACCESS_LEVEL_DISABLED, UserHelper::ACCESS_LEVEL_READONLY).") AND feed_url = :feed $login_thresh_qpart $update_limit_qpart @@ -352,6 +354,19 @@ class RSSUtils { if (!$feed_language) $feed_language = mb_strtolower(get_pref(Prefs::DEFAULT_SEARCH_LANGUAGE, $feed_obj->owner_uid)); if (!$feed_language) $feed_language = 'simple'; + $user = ORM::for_table('ttrss_users')->find_one($feed_obj->owner_uid); + + if ($user) { + if ($user->access_level == UserHelper::ACCESS_LEVEL_READONLY) { + Debug::log("error: denied update for $feed: permission denied by owner access level"); + return false; + } + } else { + // this would indicate database corruption of some kind + Debug::log("error: owner not found for feed: $feed"); + return false; + } + } else { Debug::log("error: feeds table record not found for feed: $feed"); return false; diff --git a/classes/userhelper.php b/classes/userhelper.php index 1cdd320a1..ea714b76b 100644 --- a/classes/userhelper.php +++ b/classes/userhelper.php @@ -17,6 +17,21 @@ class UserHelper { self::HASH_ALGO_SHA1 ]; + /** forbidden to login */ + const ACCESS_LEVEL_DISABLED = -2; + + /** can't subscribe to new feeds, feeds are not updated */ + const ACCESS_LEVEL_READONLY = -1; + + /** no restrictions, regular user */ + const ACCESS_LEVEL_USER = 0; + + /** not used, same as regular user */ + const ACCESS_LEVEL_POWERUSER = 5; + + /** has administrator permissions */ + const ACCESS_LEVEL_ADMIN = 10; + static function authenticate(string $login = null, string $password = null, bool $check_only = false, string $service = null) { if (!Config::get(Config::SINGLE_USER_MODE)) { $user_id = false; @@ -41,7 +56,7 @@ class UserHelper { $user = ORM::for_table('ttrss_users')->find_one($user_id); - if ($user) { + if ($user && $user->access_level != self::ACCESS_LEVEL_DISABLED) { $_SESSION["uid"] = $user_id; $_SESSION["auth_module"] = $auth_module; $_SESSION["name"] = $user->login; @@ -68,7 +83,7 @@ class UserHelper { $_SESSION["uid"] = 1; $_SESSION["name"] = "admin"; - $_SESSION["access_level"] = 10; + $_SESSION["access_level"] = self::ACCESS_LEVEL_ADMIN; $_SESSION["hide_hello"] = true; $_SESSION["hide_logout"] = true; diff --git a/include/sessions.php b/include/sessions.php index 52ab80b71..26f4e0bca 100644 --- a/include/sessions.php +++ b/include/sessions.php @@ -1,7 +1,9 @@ <?php namespace Sessions; - require_once "autoload.php"; +use UserHelper; + +require_once "autoload.php"; require_once "functions.php"; require_once "errorhandler.php"; require_once "lib/gettext/gettext.inc.php"; @@ -42,6 +44,11 @@ $_SESSION["login_error_msg"] = __("Session failed to validate (password changed)"); return false; } + + if ($user->access_level == UserHelper::ACCESS_LEVEL_DISABLED) { + $_SESSION["login_error_msg"] = __("Session failed to validate (account is disabled)"); + return false; + } } else { $_SESSION["login_error_msg"] = __("Session failed to validate (user not found)"); return false; @@ -17,6 +17,9 @@ const App = { hotkey_actions: {}, is_prefs: false, LABEL_BASE_INDEX: -1024, + UserAccessLevels: { + ACCESS_LEVEL_READONLY: -1 + }, _translations: {}, Hash: { get: function() { @@ -76,10 +79,15 @@ const App = { </select> ` }, - select_hash: function(name, value, values = {}, attributes = {}, id = "") { + select_hash: function(name, value, values = {}, attributes = {}, id = "", params = {}) { + let keys = Object.keys(values); + + if (params.numeric_sort) + keys = keys.sort((a,b) => a - b); + return ` <select name="${name}" dojoType="fox.form.Select" id="${App.escapeHtml(id)}" ${this.attributes_to_string(attributes)}> - ${Object.keys(values).map((vk) => + ${keys.map((vk) => `<option ${vk == value ? 'selected="selected"' : ''} value="${App.escapeHtml(vk)}">${App.escapeHtml(values[vk])}</option>` ).join("")} </select> diff --git a/js/CommonDialogs.js b/js/CommonDialogs.js index a68dc8068..4c169b026 100644 --- a/js/CommonDialogs.js +++ b/js/CommonDialogs.js @@ -131,6 +131,9 @@ const CommonDialogs = { console.log(rc); switch (parseInt(rc['code'])) { + case 0: + dialog.show_error(__("You are already subscribed to this feed.")); + break; case 1: dialog.hide(); Notify.info(__("Subscribed to %s").replace("%s", feed_url)); @@ -175,8 +178,11 @@ const CommonDialogs = { case 6: dialog.show_error(__("XML validation failed: %s").replace("%s", rc['message'])); break; - case 0: - dialog.show_error(__("You are already subscribed to this feed.")); + case 7: + dialog.show_error(__("Error while creating feed database entry.")); + break; + case 8: + dialog.show_error(__("You are not allowed to perform this operation.")); break; } @@ -451,6 +457,7 @@ const CommonDialogs = { xhr.json("backend.php", {op: "pref-feeds", method: "editfeed", id: feed_id}, (reply) => { const feed = reply.feed; + const is_readonly = reply.user.access_level == App.UserAccessLevels.ACCESS_LEVEL_READONLY; // for unsub prompt dialog.feed_title = feed.title; @@ -524,7 +531,9 @@ const CommonDialogs = { <fieldset> <label>${__("Update interval:")}</label> - ${App.FormFields.select_hash("update_interval", feed.update_interval, reply.intervals.update)} + ${App.FormFields.select_hash("update_interval", is_readonly ? -1 : feed.update_interval, + reply.intervals.update, + {disabled: is_readonly})} </fieldset> <fieldset> <label>${__('Article purging:')}</label> diff --git a/js/PrefUsers.js b/js/PrefUsers.js index 7ce3cae94..a6081f35f 100644 --- a/js/PrefUsers.js +++ b/js/PrefUsers.js @@ -75,7 +75,7 @@ const Users = { <fieldset> <label>${__('Access level: ')}</label> ${App.FormFields.select_hash("access_level", - user.access_level, reply.access_level_names, {disabled: admin_disabled.toString()})} + user.access_level, reply.access_level_names, {disabled: admin_disabled.toString()}, "", {numeric_sort: true})} ${admin_disabled ? App.FormFields.hidden_tag("access_level", user.access_level.toString()) : ''} @@ -148,7 +148,7 @@ style="padding : 0px" href="backend.php?op=pref-labels" title="<i class='material-icons'>label_outline1</i> <?= __('Labels') ?>"></div> - <?php if ($_SESSION["access_level"] >= 10) { ?> + <?php if ($_SESSION["access_level"] >= UserHelper::ACCESS_LEVEL_ADMIN) { ?> <div id="usersTab" dojoType="dijit.layout.ContentPane" style="padding : 0px" href="backend.php?op=pref-users" |