summaryrefslogtreecommitdiff
path: root/classes/pref/prefs.php
diff options
context:
space:
mode:
Diffstat (limited to 'classes/pref/prefs.php')
-rw-r--r--classes/pref/prefs.php266
1 files changed, 242 insertions, 24 deletions
diff --git a/classes/pref/prefs.php b/classes/pref/prefs.php
index e7e7a365e..4214ac6a8 100644
--- a/classes/pref/prefs.php
+++ b/classes/pref/prefs.php
@@ -118,10 +118,20 @@ class Pref_Prefs extends Handler_Protected {
function changepassword() {
+ if (defined('_TTRSS_DEMO_INSTANCE')) {
+ print "ERROR: ".format_error("Disabled in demo version.");
+ return;
+ }
+
$old_pw = clean($_POST["old_password"]);
$new_pw = clean($_POST["new_password"]);
$con_pw = clean($_POST["confirm_password"]);
+ if ($old_pw == $new_pw) {
+ print "ERROR: ".format_error("New password must be different from the old one.");
+ return;
+ }
+
if ($old_pw == "") {
print "ERROR: ".format_error("Old password cannot be blank.");
return;
@@ -194,6 +204,37 @@ class Pref_Prefs extends Handler_Protected {
$full_name = clean($_POST["full_name"]);
$active_uid = $_SESSION["uid"];
+ $sth = $this->pdo->prepare("SELECT email, login, full_name FROM ttrss_users WHERE id = ?");
+ $sth->execute([$active_uid]);
+
+ if ($row = $sth->fetch()) {
+ $old_email = $row["email"];
+
+ if ($old_email != $email) {
+ $mailer = new Mailer();
+
+ require_once "lib/MiniTemplator.class.php";
+
+ $tpl = new MiniTemplator;
+
+ $tpl->readTemplateFromFile("templates/mail_change_template.txt");
+
+ $tpl->setVariable('LOGIN', $row["login"]);
+ $tpl->setVariable('NEWMAIL', $email);
+ $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
+
+ $tpl->addBlock('message');
+
+ $tpl->generateOutputToString($message);
+
+ $mailer->mail(["to_name" => $row["login"],
+ "to_address" => $row["email"],
+ "subject" => "[tt-rss] Mail address change notification",
+ "message" => $message]);
+
+ }
+ }
+
$sth = $this->pdo->prepare("UPDATE ttrss_users SET email = ?,
full_name = ? WHERE id = ?");
$sth->execute([$email, $full_name, $active_uid]);
@@ -359,6 +400,30 @@ class Pref_Prefs extends Handler_Protected {
print "</form>";
print "</div>"; # content pane
+
+ if ($_SESSION["auth_module"] == "auth_internal") {
+
+ print "<div dojoType='dijit.layout.ContentPane' title=\"" . __('App passwords') . "\">";
+
+ print_notice("You can create separate passwords for API clients. Using one is required if you enable OTP.");
+
+ print "<div id='app_passwords_holder'>";
+ $this->appPasswordList();
+ print "</div>";
+
+ print "<hr>";
+
+ print "<button style='float : left' class='alt-primary' dojoType='dijit.form.Button'
+ onclick=\"Helpers.AppPasswords.generate()\">" .
+ __('Generate new password') . "</button> ";
+
+ print "<button style='float : left' class='alt-danger' dojoType='dijit.form.Button'
+ onclick=\"Helpers.AppPasswords.removeSelected()\">" .
+ __('Remove selected passwords') . "</button>";
+
+ print "</div>"; # content pane
+ }
+
print "<div dojoType='dijit.layout.ContentPane' title=\"".__('One time passwords / Authenticator')."\">";
if ($_SESSION["auth_module"] == "auth_internal") {
@@ -403,17 +468,30 @@ class Pref_Prefs extends Handler_Protected {
print "</form>";
- } else if (function_exists("imagecreatefromstring")) {
+ } else {
print_warning("You will need a compatible Authenticator to use this. Changing your password would automatically disable OTP.");
- print_notice("Scan the following code by the Authenticator application:");
+ print_notice("You will need to generate app passwords for the API clients if you enable OTP.");
- $csrf_token = $_SESSION["csrf_token"];
+ if (function_exists("imagecreatefromstring")) {
+ print "<h3>" . __("Scan the following code by the Authenticator application or copy the key manually") . "</h3>";
- print "<img alt='otp qr-code' src='backend.php?op=pref-prefs&method=otpqrcode&csrf_token=$csrf_token'>";
+ $csrf_token = $_SESSION["csrf_token"];
+ print "<img alt='otp qr-code' src='backend.php?op=pref-prefs&method=otpqrcode&csrf_token=$csrf_token'>";
+ } else {
+ print_error("PHP GD functions are required to generate QR codes.");
+ print "<h3>" . __("Use the following OTP key with a compatible Authenticator application") . "</h3>";
+ }
print "<form dojoType='dijit.form.Form' id='changeOtpForm'>";
+ $otp_secret = $this->otpsecret();
+
+ print "<fieldset>";
+ print "<label>".__("OTP Key:")."</label>";
+ print "<input dojoType='dijit.form.ValidationTextBox' disabled='disabled' value='$otp_secret' size='32'>";
+ print "</fieldset>";
+
print_hidden("op", "pref-prefs");
print_hidden("method", "otpenable");
@@ -454,8 +532,6 @@ class Pref_Prefs extends Handler_Protected {
print "</form>";
- } else {
- print_notice("PHP GD functions are required for OTP support.");
}
}
@@ -773,6 +849,24 @@ class Pref_Prefs extends Handler_Protected {
print_warning("Your PHP configuration has open_basedir restrictions enabled. Some plugins relying on CURL for functionality may not work correctly.");
}
+ $feed_handler_whitelist = [ "Af_Comics" ];
+
+ $feed_handlers = array_merge(
+ PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FEED_FETCHED),
+ PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FEED_PARSED),
+ PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FETCH_FEED));
+
+ $feed_handlers = array_filter($feed_handlers, function($plugin) use ($feed_handler_whitelist) {
+ return in_array(get_class($plugin), $feed_handler_whitelist) === FALSE; });
+
+ if (count($feed_handlers) > 0) {
+ print_error(
+ T_sprintf("The following plugins use per-feed content hooks. This may cause excessive data usage and origin server load resulting in a ban of your instance: <b>%s</b>" ,
+ implode(", ", array_map(function($plugin) { return get_class($plugin); }, $feed_handlers))
+ ) . " (<a href='https://tt-rss.org/wiki/FeedHandlerPlugins' target='_blank'>".__("More info...")."</a>)"
+ );
+ }
+
print "<h2>".__("System plugins")."</h2>";
print_notice("System plugins are enabled in <strong>config.php</strong> for all users.");
@@ -886,27 +980,41 @@ class Pref_Prefs extends Handler_Protected {
$_SESSION["prefs_show_advanced"] = !$_SESSION["prefs_show_advanced"];
}
- function otpqrcode() {
- require_once "lib/phpqrcode/phpqrcode.php";
-
- $sth = $this->pdo->prepare("SELECT login,salt,otp_enabled
+ function otpsecret() {
+ $sth = $this->pdo->prepare("SELECT salt, otp_enabled
FROM ttrss_users
WHERE id = ?");
$sth->execute([$_SESSION['uid']]);
if ($row = $sth->fetch()) {
-
- $base32 = new \OTPHP\Base32();
-
- $login = $row["login"];
$otp_enabled = sql_bool_to_bool($row["otp_enabled"]);
if (!$otp_enabled) {
- $secret = $base32->encode(sha1($row["salt"]));
+ $base32 = new \OTPHP\Base32();
+ $secret = $base32->encode(mb_substr(sha1($row["salt"]), 0, 12), false);
+
+ return $secret;
+ }
+ }
+
+ return false;
+ }
+
+ function otpqrcode() {
+ require_once "lib/phpqrcode/phpqrcode.php";
+ $sth = $this->pdo->prepare("SELECT login
+ FROM ttrss_users
+ WHERE id = ?");
+ $sth->execute([$_SESSION['uid']]);
+
+ if ($row = $sth->fetch()) {
+ $secret = $this->otpsecret();
+ $login = $row['login'];
+
+ if ($secret) {
QRcode::png("otpauth://totp/".urlencode($login).
"?secret=$secret&issuer=".urlencode("Tiny Tiny RSS"));
-
}
}
}
@@ -920,16 +1028,12 @@ class Pref_Prefs extends Handler_Protected {
if ($authenticator->check_password($_SESSION["uid"], $password)) {
- $sth = $this->pdo->prepare("SELECT salt
- FROM ttrss_users
- WHERE id = ?");
- $sth->execute([$_SESSION['uid']]);
+ $secret = $this->otpsecret();
- if ($row = $sth->fetch()) {
+ if ($secret) {
$base32 = new \OTPHP\Base32();
- $secret = $base32->encode(sha1($row["salt"]));
$topt = new \OTPHP\TOTP($secret);
$otp_check = $topt->now();
@@ -972,6 +1076,31 @@ class Pref_Prefs extends Handler_Protected {
if ($authenticator->check_password($_SESSION["uid"], $password)) {
+ $sth = $this->pdo->prepare("SELECT email, login FROM ttrss_users WHERE id = ?");
+ $sth->execute([$_SESSION['uid']]);
+
+ if ($row = $sth->fetch()) {
+ $mailer = new Mailer();
+
+ require_once "lib/MiniTemplator.class.php";
+
+ $tpl = new MiniTemplator;
+
+ $tpl->readTemplateFromFile("templates/otp_disabled_template.txt");
+
+ $tpl->setVariable('LOGIN', $row["login"]);
+ $tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
+
+ $tpl->addBlock('message');
+
+ $tpl->generateOutputToString($message);
+
+ $mailer->mail(["to_name" => $row["login"],
+ "to_address" => $row["email"],
+ "subject" => "[tt-rss] OTP change notification",
+ "message" => $message]);
+ }
+
$sth = $this->pdo->prepare("UPDATE ttrss_users SET otp_enabled = false WHERE
id = ?");
$sth->execute([$_SESSION['uid']]);
@@ -1008,12 +1137,18 @@ class Pref_Prefs extends Handler_Protected {
print_hidden("method", "setpref");
print_hidden("key", "USER_STYLESHEET");
+ print "<div id='css_edit_apply_msg' style='display : none'>";
+ print_warning(__("User CSS has been applied, you might need to reload the page to see all changes."));
+ print "</div>";
+
print "<textarea class='panel user-css-editor' dojoType='dijit.form.SimpleTextarea'
style='font-size : 12px;' name='value'>$value</textarea>";
print "<footer>";
- print "<button dojoType='dijit.form.Button'
- onclick=\"dijit.byId('cssEditDlg').execute()\">".__('Save')."</button> ";
+ print "<button dojoType='dijit.form.Button' class='alt-success'
+ onclick=\"dijit.byId('cssEditDlg').apply()\">".__('Apply')."</button> ";
+ print "<button dojoType='dijit.form.Button' class='alt-primary'
+ onclick=\"dijit.byId('cssEditDlg').execute()\">".__('Save and reload')."</button> ";
print "<button dojoType='dijit.form.Button'
onclick=\"dijit.byId('cssEditDlg').hide()\">".__('Cancel')."</button>";
print "</footer>";
@@ -1130,4 +1265,87 @@ class Pref_Prefs extends Handler_Protected {
}
return "";
}
+
+ private function appPasswordList() {
+ print "<div dojoType='fox.Toolbar'>";
+ print "<div dojoType='fox.form.DropDownButton'>" .
+ "<span>" . __('Select') . "</span>";
+ print "<div dojoType='dijit.Menu' style='display: none'>";
+ print "<div onclick=\"Tables.select('app-password-list', true)\"
+ dojoType=\"dijit.MenuItem\">" . __('All') . "</div>";
+ print "<div onclick=\"Tables.select('app-password-list', false)\"
+ dojoType=\"dijit.MenuItem\">" . __('None') . "</div>";
+ print "</div></div>";
+ print "</div>"; #toolbar
+
+ print "<div class='panel panel-scrollable'>";
+ print "<table width='100%' id='app-password-list'>";
+ print "<tr>";
+ print "<th width='2%'></th>";
+ print "<th align='left'>".__("Description")."</th>";
+ print "<th align='right'>".__("Created")."</th>";
+ print "<th align='right'>".__("Last used")."</th>";
+ print "</tr>";
+
+ $sth = $this->pdo->prepare("SELECT id, title, created, last_used
+ FROM ttrss_app_passwords WHERE owner_uid = ?");
+ $sth->execute([$_SESSION['uid']]);
+
+ while ($row = $sth->fetch()) {
+
+ $row_id = $row["id"];
+
+ print "<tr data-row-id='$row_id'>";
+
+ print "<td align='center'>
+ <input onclick='Tables.onRowChecked(this)' dojoType='dijit.form.CheckBox' type='checkbox'></td>";
+ print "<td>" . htmlspecialchars($row["title"]) . "</td>";
+
+ print "<td align='right' class='text-muted'>";
+ print make_local_datetime($row['created'], false);
+ print "</td>";
+
+ print "<td align='right' class='text-muted'>";
+ print make_local_datetime($row['last_used'], false);
+ print "</td>";
+
+ print "</tr>";
+ }
+
+ print "</table>";
+ print "</div>";
+ }
+
+ private function encryptAppPassword($password) {
+ $salt = substr(bin2hex(get_random_bytes(24)), 0, 24);
+
+ return "SSHA-512:".hash('sha512', $salt . $password). ":$salt";
+ }
+
+ function deleteAppPassword() {
+ $ids = explode(",", clean($_REQUEST['ids']));
+ $ids_qmarks = arr_qmarks($ids);
+
+ $sth = $this->pdo->prepare("DELETE FROM ttrss_app_passwords WHERE id IN ($ids_qmarks) AND owner_uid = ?");
+ $sth->execute(array_merge($ids, [$_SESSION['uid']]));
+
+ $this->appPasswordList();
+ }
+
+ function generateAppPassword() {
+ $title = clean($_REQUEST['title']);
+ $new_password = make_password(16);
+ $new_password_hash = $this->encryptAppPassword($new_password);
+
+ print_warning(T_sprintf("Generated password <strong>%s</strong> for %s. Please remember it for future reference.", $new_password, $title));
+
+ $sth = $this->pdo->prepare("INSERT INTO ttrss_app_passwords
+ (title, pwd_hash, service, created, owner_uid)
+ VALUES
+ (?, ?, ?, NOW(), ?)");
+
+ $sth->execute([$title, $new_password_hash, Auth_Base::AUTH_SERVICE_API, $_SESSION['uid']]);
+
+ $this->appPasswordList();
+ }
}