Browse Source

add placeholder authentication via app passwords if service is passed
forbid logins via regular passwords for services
remove AUTH_DISABLE_OTP

Andrew Dolgov 2 months ago
parent
commit
68b0380118
6 changed files with 144 additions and 127 deletions
  1. 0 2
      api/index.php
  2. 2 2
      classes/api.php
  3. 4 2
      classes/auth/base.php
  4. 1 1
      classes/iauthmodule.php
  5. 2 2
      include/functions.php
  6. 135 118
      plugins/auth_internal/init.php

+ 0 - 2
api/index.php

@@ -22,8 +22,6 @@
 	ini_set('session.use_cookies', 0);
 	ini_set("session.gc_maxlifetime", 86400);
 
-	define('AUTH_DISABLE_OTP', true);
-
 	if (defined('ENABLE_GZIP_OUTPUT') && ENABLE_GZIP_OUTPUT &&
 			function_exists("ob_gzhandler")) {
 

+ 2 - 2
classes/api.php

@@ -74,10 +74,10 @@ class API extends Handler {
 		}
 
 		if (get_pref("ENABLE_API_ACCESS", $uid)) {
-			if (authenticate_user($login, $password)) {               // try login with normal password
+			if (authenticate_user($login, $password, false,  Auth_Base::AUTH_SERVICE_API)) {               // try login with normal password
 				$this->wrap(self::STATUS_OK, array("session_id" => session_id(),
 					"api_level" => self::API_LEVEL));
-			} else if (authenticate_user($login, $password_base64)) { // else try with base64_decoded password
+			} else if (authenticate_user($login, $password_base64, false, Auth_Base::AUTH_SERVICE_API)) { // else try with base64_decoded password
 				$this->wrap(self::STATUS_OK,	array("session_id" => session_id(),
 					"api_level" => self::API_LEVEL));
 			} else {                                                         // else we are not logged in

+ 4 - 2
classes/auth/base.php

@@ -2,6 +2,8 @@
 class Auth_Base {
 	private $pdo;
 
+	const AUTH_SERVICE_API = '_api';
+
 	function __construct() {
 		$this->pdo = Db::pdo();
 	}
@@ -9,14 +11,14 @@ class Auth_Base {
 	/**
 	 * @SuppressWarnings(unused)
 	 */
-	function check_password($owner_uid, $password) {
+	function check_password($owner_uid, $password, $service = '') {
 		return false;
 	}
 
 	/**
 	 * @SuppressWarnings(unused)
 	 */
-	function authenticate($login, $password) {
+	function authenticate($login, $password, $service = '') {
 		return false;
 	}
 

+ 1 - 1
classes/iauthmodule.php

@@ -1,4 +1,4 @@
 <?php
 interface IAuthModule {
-	function authenticate($login, $password);
+	function authenticate($login, $password); // + optional third parameter: $service
 }

+ 2 - 2
include/functions.php

@@ -509,7 +509,7 @@
 		return "";
 	}
 
-	function authenticate_user($login, $password, $check_only = false) {
+	function authenticate_user($login, $password, $check_only = false, $service = false) {
 
 		if (!SINGLE_USER_MODE) {
 			$user_id = false;
@@ -517,7 +517,7 @@
 
 			foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_AUTH_USER) as $plugin) {
 
-				$user_id = (int) $plugin->authenticate($login, $password);
+				$user_id = (int) $plugin->authenticate($login, $password, $service);
 
 				if ($user_id) {
 					$auth_module = strtolower(get_class($plugin));

+ 135 - 118
plugins/auth_internal/init.php

@@ -1,31 +1,30 @@
-<?php
-class Auth_Internal extends Plugin implements IAuthModule {
+	<?php
+	class Auth_Internal extends Plugin implements IAuthModule {
 
-	private $host;
+		private $host;
 
-	function about() {
-		return array(1.0,
-			"Authenticates against internal tt-rss database",
-			"fox",
-			true);
-	}
+		function about() {
+			return array(1.0,
+				"Authenticates against internal tt-rss database",
+				"fox",
+				true);
+		}
 
-    /* @var PluginHost $host */
-    function init($host) {
-		$this->host = $host;
-		$this->pdo = Db::pdo();
+		/* @var PluginHost $host */
+		function init($host) {
+			$this->host = $host;
+			$this->pdo = Db::pdo();
 
-		$host->add_hook($host::HOOK_AUTH_USER, $this);
-	}
+			$host->add_hook($host::HOOK_AUTH_USER, $this);
+		}
 
-	function authenticate($login, $password) {
+		function authenticate($login, $password, $service = '') {
 
-		$pwd_hash1 = encrypt_password($password);
-		$pwd_hash2 = encrypt_password($password, $login);
-		$otp = $_REQUEST["otp"];
+			$pwd_hash1 = encrypt_password($password);
+			$pwd_hash2 = encrypt_password($password, $login);
+			$otp = $_REQUEST["otp"];
 
-		if (get_schema_version() > 96) {
-			if (!defined('AUTH_DISABLE_OTP') || !AUTH_DISABLE_OTP) {
+			if (get_schema_version() > 96) {
 
 				$sth = $this->pdo->prepare("SELECT otp_enabled,salt FROM ttrss_users WHERE
 					login = ?");
@@ -42,6 +41,12 @@ class Auth_Internal extends Plugin implements IAuthModule {
 					$otp_check = $topt->now();
 
 					if ($otp_enabled) {
+
+						// only allow app password checking if OTP is enabled
+						if ($service && get_schema_version() > 138) {
+							return $this->check_app_password($login, $password, $service);
+						}
+
 						if ($otp) {
 							if ($otp != $otp_check) {
 								return false;
@@ -83,61 +88,81 @@ class Auth_Internal extends Plugin implements IAuthModule {
 					}
 				}
 			}
-		}
 
-		if (get_schema_version() > 87) {
+			// check app passwords first but allow regular password as a fallback for the time being
+			// if OTP is not enabled
 
-			$sth = $this->pdo->prepare("SELECT salt FROM ttrss_users WHERE login = ?");
-			$sth->execute([$login]);
+			if ($service && get_schema_version() > 138) {
+				$user_id = $this->check_app_password($login, $password, $service);
 
-			if ($row = $sth->fetch()) {
-				$salt = $row['salt'];
+				if ($user_id)
+					return $user_id;
+			}
 
-				if ($salt == "") {
+			if (get_schema_version() > 87) {
 
-					$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
-                        login = ? AND (pwd_hash = ? OR pwd_hash = ?)");
+				$sth = $this->pdo->prepare("SELECT salt FROM ttrss_users WHERE login = ?");
+				$sth->execute([$login]);
 
-					$sth->execute([$login, $pwd_hash1, $pwd_hash2]);
+				if ($row = $sth->fetch()) {
+					$salt = $row['salt'];
 
-					// verify and upgrade password to new salt base
+					if ($salt == "") {
 
-					if ($row = $sth->fetch()) {
-						// upgrade password to MODE2
+						$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
+							login = ? AND (pwd_hash = ? OR pwd_hash = ?)");
 
-						$user_id = $row['id'];
+						$sth->execute([$login, $pwd_hash1, $pwd_hash2]);
 
-						$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
-						$pwd_hash = encrypt_password($password, $salt, true);
+						// verify and upgrade password to new salt base
+
+						if ($row = $sth->fetch()) {
+							// upgrade password to MODE2
+
+							$user_id = $row['id'];
 
-						$sth = $this->pdo->prepare("UPDATE ttrss_users SET
-					        pwd_hash = ?, salt = ? WHERE login = ?");
+							$salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
+							$pwd_hash = encrypt_password($password, $salt, true);
 
-						$sth->execute([$pwd_hash, $salt, $login]);
+							$sth = $this->pdo->prepare("UPDATE ttrss_users SET
+								pwd_hash = ?, salt = ? WHERE login = ?");
 
-						return $user_id;
+							$sth->execute([$pwd_hash, $salt, $login]);
+
+							return $user_id;
+
+						} else {
+							return false;
+						}
 
 					} else {
-						return false;
+						$pwd_hash = encrypt_password($password, $salt, true);
+
+						$sth = $this->pdo->prepare("SELECT id
+							  FROM ttrss_users WHERE
+							  login = ? AND pwd_hash = ?");
+						$sth->execute([$login, $pwd_hash]);
+
+						if ($row = $sth->fetch()) {
+							return $row['id'];
+						}
 					}
 
 				} else {
-					$pwd_hash = encrypt_password($password, $salt, true);
-
 					$sth = $this->pdo->prepare("SELECT id
-  		                  FROM ttrss_users WHERE
-					      login = ? AND pwd_hash = ?");
-					$sth->execute([$login, $pwd_hash]);
+						FROM ttrss_users WHERE
+						  login = ? AND (pwd_hash = ? OR pwd_hash = ?)");
+
+					$sth->execute([$login, $pwd_hash1, $pwd_hash2]);
 
 					if ($row = $sth->fetch()) {
 						return $row['id'];
 					}
 				}
-
 			} else {
 				$sth = $this->pdo->prepare("SELECT id
-                    FROM ttrss_users WHERE
-                      login = ? AND (pwd_hash = ? OR pwd_hash = ?)");
+						FROM ttrss_users WHERE
+						  login = ? AND (pwd_hash = ? OR pwd_hash = ?)");
 
 				$sth->execute([$login, $pwd_hash1, $pwd_hash2]);
 
@@ -145,107 +170,99 @@ class Auth_Internal extends Plugin implements IAuthModule {
 					return $row['id'];
 				}
 			}
-		} else {
-			$sth = $this->pdo->prepare("SELECT id
-                    FROM ttrss_users WHERE
-                      login = ? AND (pwd_hash = ? OR pwd_hash = ?)");
 
-			$sth->execute([$login, $pwd_hash1, $pwd_hash2]);
-
-			if ($row = $sth->fetch()) {
-				return $row['id'];
-			}
-        }
-
-		return false;
-	}
+			return false;
+		}
 
-	function check_password($owner_uid, $password) {
+		function check_password($owner_uid, $password) {
 
-		$sth = $this->pdo->prepare("SELECT salt,login FROM ttrss_users WHERE
-			id = ?");
-		$sth->execute([$owner_uid]);
+			$sth = $this->pdo->prepare("SELECT salt,login,otp_enabled FROM ttrss_users WHERE
+				id = ?");
+			$sth->execute([$owner_uid]);
 
-		if ($row = $sth->fetch()) {
+			if ($row = $sth->fetch()) {
 
-			$salt = $row['salt'];
-			$login = $row['login'];
+				$salt = $row['salt'];
+				$login = $row['login'];
 
-			if (!$salt) {
-				$password_hash1 = encrypt_password($password);
-				$password_hash2 = encrypt_password($password, $login);
+				if (!$salt) {
+					$password_hash1 = encrypt_password($password);
+					$password_hash2 = encrypt_password($password, $login);
 
-				$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
-                    id = ? AND (pwd_hash = ? OR pwd_hash = ?)");
+					$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
+						id = ? AND (pwd_hash = ? OR pwd_hash = ?)");
 
-				$sth->execute([$owner_uid, $password_hash1, $password_hash2]);
+					$sth->execute([$owner_uid, $password_hash1, $password_hash2]);
 
-				return $sth->fetch();
+					return $sth->fetch();
 
-			} else {
-				$password_hash = encrypt_password($password, $salt, true);
+				} else {
+					$password_hash = encrypt_password($password, $salt, true);
 
-				$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
-                    id = ? AND pwd_hash = ?");
+					$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
+						id = ? AND pwd_hash = ?");
 
-				$sth->execute([$owner_uid, $password_hash]);
+					$sth->execute([$owner_uid, $password_hash]);
 
-				return $sth->fetch();
+					return $sth->fetch();
+				}
 			}
+
+			return false;
 		}
 
-        return false;
-	}
+		function change_password($owner_uid, $old_password, $new_password) {
 
-	function change_password($owner_uid, $old_password, $new_password) {
+			if ($this->check_password($owner_uid, $old_password)) {
 
-		if ($this->check_password($owner_uid, $old_password)) {
+				$new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
+				$new_password_hash = encrypt_password($new_password, $new_salt, true);
 
-			$new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
-			$new_password_hash = encrypt_password($new_password, $new_salt, true);
+				$sth = $this->pdo->prepare("UPDATE ttrss_users SET
+					pwd_hash = ?, salt = ?, otp_enabled = false
+						WHERE id = ?");
+				$sth->execute([$new_password_hash, $new_salt, $owner_uid]);
 
-			$sth = $this->pdo->prepare("UPDATE ttrss_users SET
-				pwd_hash = ?, salt = ?, otp_enabled = false
-					WHERE id = ?");
-			$sth->execute([$new_password_hash, $new_salt, $owner_uid]);
+				$_SESSION["pwd_hash"] = $new_password_hash;
 
-			$_SESSION["pwd_hash"] = $new_password_hash;
+				$sth = $this->pdo->prepare("SELECT email, login FROM ttrss_users WHERE id = ?");
+				$sth->execute([$owner_uid]);
 
-			$sth = $this->pdo->prepare("SELECT email, login FROM ttrss_users WHERE id = ?");
-			$sth->execute([$owner_uid]);
+				if ($row = $sth->fetch()) {
+					$mailer = new Mailer();
 
-			if ($row = $sth->fetch()) {
-				$mailer = new Mailer();
+					require_once "lib/MiniTemplator.class.php";
 
-				require_once "lib/MiniTemplator.class.php";
+					$tpl = new MiniTemplator;
 
-				$tpl = new MiniTemplator;
+					$tpl->readTemplateFromFile("templates/password_change_template.txt");
 
-				$tpl->readTemplateFromFile("templates/password_change_template.txt");
+					$tpl->setVariable('LOGIN', $row["login"]);
+					$tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
 
-				$tpl->setVariable('LOGIN', $row["login"]);
-				$tpl->setVariable('TTRSS_HOST', SELF_URL_PATH);
+					$tpl->addBlock('message');
 
-				$tpl->addBlock('message');
+					$tpl->generateOutputToString($message);
 
-				$tpl->generateOutputToString($message);
+					$mailer->mail(["to_name" => $row["login"],
+						"to_address" => $row["email"],
+						"subject" => "[tt-rss] Password change notification",
+						"message" => $message]);
 
-				$mailer->mail(["to_name" => $row["login"],
-					"to_address" => $row["email"],
-					"subject" => "[tt-rss] Password change notification",
-					"message" => $message]);
+				}
 
+				return __("Password has been changed.");
+			} else {
+				return "ERROR: ".__('Old password is incorrect.');
 			}
+		}
 
-			return __("Password has been changed.");
-		} else {
-			return "ERROR: ".__('Old password is incorrect.');
+		private function check_app_password($login, $password, $service) {
+			return false;
 		}
-	}
 
-	function api_version() {
-		return 2;
-	}
+		function api_version() {
+			return 2;
+		}
 
-}
-?>
+	}