Jelajahi Sumber

Minz allow parallel sessions (#3096)

* Minz allow parallel sessions

#fix https://github.com/FreshRSS/FreshRSS/issues/3093

* Array optimisation

* Array optimisation missing

* Reduce direct access to $_SESSION except in install process

* Fix session start headers warning

* Use cookie only the first time the session is started:
`PHP Warning:  session_start(): Cannot start session when headers
already sent in /var/www/FreshRSS/lib/Minz/Session.php on line 39`

* New concept of volatile session for API calls

Optimisation: do not use cookies or local storage at all for API calls
without a Web session
Fix warning:

```
PHP Warning:  session_destroy(): Trying to destroy uninitialized session
in Unknown on line 0
```

* Only call Minz_Session::init once in our index

It was called twice (once indirectly via FreshRSS->init())

* Whitespace

* Mutex for notifications

Implement mutex for notifications
https://github.com/FreshRSS/FreshRSS/pull/3208#discussion_r499509809

* Typo

* Install script is not ready for using Minz_Session
Alexandre Alapetite 5 tahun lalu
induk
melakukan
0319cc9d23

+ 10 - 6
app/Controllers/authController.php

@@ -141,9 +141,11 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			);
 			);
 			if ($ok) {
 			if ($ok) {
 				// Set session parameter to give access to the user.
 				// Set session parameter to give access to the user.
-				Minz_Session::_param('currentUser', $username);
-				Minz_Session::_param('passwordHash', $conf->passwordHash);
-				Minz_Session::_param('csrf');
+				Minz_Session::_params([
+					'currentUser' => $username,
+					'passwordHash' => $conf->passwordHash,
+					'csrf' => false,
+				]);
 				FreshRSS_Auth::giveAccess();
 				FreshRSS_Auth::giveAccess();
 
 
 				// Set cookie parameter if nedded.
 				// Set cookie parameter if nedded.
@@ -190,9 +192,11 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			$ok = password_verify($password, $s);
 			$ok = password_verify($password, $s);
 			unset($password);
 			unset($password);
 			if ($ok) {
 			if ($ok) {
-				Minz_Session::_param('currentUser', $username);
-				Minz_Session::_param('passwordHash', $s);
-				Minz_Session::_param('csrf');
+				Minz_Session::_params([
+					'currentUser' => $username,
+					'passwordHash' => $s,
+					'csrf' => false,
+				]);
 				FreshRSS_Auth::giveAccess();
 				FreshRSS_Auth::giveAccess();
 
 
 				Minz_Translate::init($conf->language);
 				Minz_Translate::init($conf->language);

+ 4 - 2
app/Controllers/errorController.php

@@ -16,8 +16,10 @@ class FreshRSS_error_Controller extends Minz_ActionController {
 	public function indexAction() {
 	public function indexAction() {
 		$code_int = Minz_Session::param('error_code', 404);
 		$code_int = Minz_Session::param('error_code', 404);
 		$error_logs = Minz_Session::param('error_logs', array());
 		$error_logs = Minz_Session::param('error_logs', array());
-		Minz_Session::_param('error_code');
-		Minz_Session::_param('error_logs');
+		Minz_Session::_params([
+			'error_code' => false,
+			'error_logs' => false,
+		]);
 
 
 		switch ($code_int) {
 		switch ($code_int) {
 		case 200 :
 		case 200 :

+ 5 - 3
app/Controllers/userController.php

@@ -350,9 +350,11 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			// get started immediately.
 			// get started immediately.
 			if ($ok && !FreshRSS_Auth::hasAccess('admin')) {
 			if ($ok && !FreshRSS_Auth::hasAccess('admin')) {
 				$user_conf = get_user_configuration($new_user_name);
 				$user_conf = get_user_configuration($new_user_name);
-				Minz_Session::_param('currentUser', $new_user_name);
-				Minz_Session::_param('passwordHash', $user_conf->passwordHash);
-				Minz_Session::_param('csrf');
+				Minz_Session::_params([
+					'currentUser' => $new_user_name,
+					'passwordHash' => $user_conf->passwordHash,
+					'csrf' => false,
+				]);
 				FreshRSS_Auth::giveAccess();
 				FreshRSS_Auth::giveAccess();
 			}
 			}
 
 

+ 22 - 12
app/Models/Auth.php

@@ -23,8 +23,10 @@ class FreshRSS_Auth {
 		if ($current_user === '') {
 		if ($current_user === '') {
 			$conf = Minz_Configuration::get('system');
 			$conf = Minz_Configuration::get('system');
 			$current_user = $conf->default_user;
 			$current_user = $conf->default_user;
-			Minz_Session::_param('currentUser', $current_user);
-			Minz_Session::_param('csrf');
+			Minz_Session::_params([
+				'currentUser' => $current_user,
+				'csrf' => false,
+			]);
 		}
 		}
 
 
 		if (self::$login_ok) {
 		if (self::$login_ok) {
@@ -55,9 +57,11 @@ class FreshRSS_Auth {
 			$current_user = '';
 			$current_user = '';
 			if (isset($credentials[1])) {
 			if (isset($credentials[1])) {
 				$current_user = trim($credentials[0]);
 				$current_user = trim($credentials[0]);
-				Minz_Session::_param('currentUser', $current_user);
-				Minz_Session::_param('passwordHash', trim($credentials[1]));
-				Minz_Session::_param('csrf');
+				Minz_Session::_params([
+					'currentUser' => $current_user,
+					'passwordHash' => trim($credentials[1]),
+					'csrf' => false,
+				]);
 			}
 			}
 			return $current_user != '';
 			return $current_user != '';
 		case 'http_auth':
 		case 'http_auth':
@@ -79,8 +83,10 @@ class FreshRSS_Auth {
 				]);
 				]);
 			}
 			}
 			if ($login_ok) {
 			if ($login_ok) {
-				Minz_Session::_param('currentUser', $current_user);
-				Minz_Session::_param('csrf');
+				Minz_Session::_params([
+					'currentUser' => $current_user,
+					'csrf' => false,
+				]);
 			}
 			}
 			return $login_ok;
 			return $login_ok;
 		case 'none':
 		case 'none':
@@ -118,8 +124,10 @@ class FreshRSS_Auth {
 			self::$login_ok = false;
 			self::$login_ok = false;
 		}
 		}
 
 
-		Minz_Session::_param('loginOk', self::$login_ok);
-		Minz_Session::_param('REMOTE_USER', httpAuthUser());
+		Minz_Session::_params([
+			'loginOk' => self::$login_ok,
+			'REMOTE_USER' => httpAuthUser(),
+		]);
 		return self::$login_ok;
 		return self::$login_ok;
 	}
 	}
 
 
@@ -153,9 +161,11 @@ class FreshRSS_Auth {
 	 */
 	 */
 	public static function removeAccess() {
 	public static function removeAccess() {
 		self::$login_ok = false;
 		self::$login_ok = false;
-		Minz_Session::_param('loginOk');
-		Minz_Session::_param('csrf');
-		Minz_Session::_param('REMOTE_USER');
+		Minz_Session::_params([
+			'loginOk' => false,
+			'csrf' => false,
+			'REMOTE_USER' => false,
+		]);
 		$system_conf = Minz_Configuration::get('system');
 		$system_conf = Minz_Configuration::get('system');
 
 
 		$username = '';
 		$username = '';

+ 5 - 7
app/Models/DatabaseDAO.php

@@ -20,11 +20,10 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
 
 
 		try {
 		try {
 			$sql = sprintf($SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']);
 			$sql = sprintf($SQL_CREATE_DB, empty($db['base']) ? '' : $db['base']);
-			return $this->pdo->exec($sql) !== false;
+			return $this->pdo->exec($sql) === false ? 'Error during CREATE DATABASE' : '';
 		} catch (Exception $e) {
 		} catch (Exception $e) {
-			$_SESSION['bd_error'] = $e->getMessage();
-			syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
-			return false;
+			syslog(LOG_DEBUG, __method__ . ' notice: ' . $e->getMessage());
+			return $e->getMessage();
 		}
 		}
 	}
 	}
 
 
@@ -33,11 +32,10 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
 			$sql = 'SELECT 1';
 			$sql = 'SELECT 1';
 			$stm = $this->pdo->query($sql);
 			$stm = $this->pdo->query($sql);
 			$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
 			$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
-			return $res != false;
+			return $res == false ? 'Error during SQL connection test!' : '';
 		} catch (Exception $e) {
 		} catch (Exception $e) {
-			$_SESSION['bd_error'] = $e->getMessage();
 			syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
 			syslog(LOG_DEBUG, __method__ . ' warning: ' . $e->getMessage());
-			return false;
+			return $e->getMessage();
 		}
 		}
 	}
 	}
 
 

+ 4 - 2
app/actualize_script.php

@@ -78,8 +78,10 @@ foreach ($users as $user) {
 		}
 		}
 	}
 	}
 
 
-	Minz_Session::_param('currentUser', '_');
-	Minz_Session::_param('loginOk');
+	Minz_Session::_params([
+		'currentUser' => '_',
+		'loginOk' => false,
+	]);
 	gc_collect_cycles();
 	gc_collect_cycles();
 }
 }
 
 

+ 8 - 3
app/install.php

@@ -163,9 +163,14 @@ function saveStep2() {
 
 
 		$ok = false;
 		$ok = false;
 		try {
 		try {
-			Minz_Session::_param('currentUser', $config_array['default_user']);
-			$ok = initDb();
-			Minz_Session::_param('currentUser');
+			$_SESSION['currentUser'] = $config_array['default_user'];
+			$error = initDb();
+			unset($_SESSION['currentUser']);
+			if ($error != '') {
+				$_SESSION['bd_error'] = $error;
+			} else {
+				$ok = true;
+			}
 		} catch (Exception $ex) {
 		} catch (Exception $ex) {
 			$_SESSION['bd_error'] = $ex->getMessage();
 			$_SESSION['bd_error'] = $ex->getMessage();
 			$ok = false;
 			$ok = false;

+ 1 - 0
cli/_cli.php

@@ -10,6 +10,7 @@ require(__DIR__ . '/../constants.php');
 require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 require(LIB_PATH . '/lib_install.php');
 require(LIB_PATH . '/lib_install.php');
 
 
+Minz_Session::init('FreshRSS', true);
 Minz_Configuration::register('system',
 Minz_Configuration::register('system',
 	DATA_PATH . '/config.php',
 	DATA_PATH . '/config.php',
 	FRESHRSS_PATH . '/config.default.php');
 	FRESHRSS_PATH . '/config.default.php');

+ 6 - 2
cli/do-install.php

@@ -93,10 +93,14 @@ Minz_Session::_param('currentUser', '_');	//Default user
 
 
 $ok = false;
 $ok = false;
 try {
 try {
-	$ok = initDb();
+	$error = initDb();
+	if ($error != '') {
+		$_SESSION['bd_error'] = $error;
+	} else {
+		$ok = true;
+	}
 } catch (Exception $ex) {
 } catch (Exception $ex) {
 	$_SESSION['bd_error'] = $ex->getMessage();
 	$_SESSION['bd_error'] = $ex->getMessage();
-	$ok = false;
 }
 }
 
 
 if (!$ok) {
 if (!$ok) {

+ 4 - 2
lib/Minz/Error.php

@@ -24,8 +24,10 @@ class Minz_Error {
 		$error_filename = APP_PATH . '/Controllers/errorController.php';
 		$error_filename = APP_PATH . '/Controllers/errorController.php';
 
 
 		if (file_exists ($error_filename)) {
 		if (file_exists ($error_filename)) {
-			Minz_Session::_param('error_code', $code);
-			Minz_Session::_param('error_logs', $logs);
+			Minz_Session::_params([
+				'error_code' => $code,
+				'error_logs' => $logs,
+			]);
 
 
 			Minz_Request::forward (array (
 			Minz_Request::forward (array (
 				'c' => 'error'
 				'c' => 'error'

+ 4 - 2
lib/Minz/Request.php

@@ -269,13 +269,14 @@ class Minz_Request {
 	}
 	}
 
 
 	private static function setNotification($type, $content) {
 	private static function setNotification($type, $content) {
-		//TODO: Will need to ensure non-concurrency when landing https://github.com/FreshRSS/FreshRSS/pull/3096
+		Minz_Session::lock();
 		$requests = Minz_Session::param('requests', []);
 		$requests = Minz_Session::param('requests', []);
 		$requests[self::requestId()] = [
 		$requests[self::requestId()] = [
 				'time' => time(),
 				'time' => time(),
 				'notification' => [ 'type' => $type, 'content' => $content ],
 				'notification' => [ 'type' => $type, 'content' => $content ],
 			];
 			];
 		Minz_Session::_param('requests', $requests);
 		Minz_Session::_param('requests', $requests);
+		Minz_Session::unlock();
 	}
 	}
 
 
 	public static function setGoodNotification($content) {
 	public static function setGoodNotification($content) {
@@ -288,7 +289,7 @@ class Minz_Request {
 
 
 	public static function getNotification() {
 	public static function getNotification() {
 		$notif = null;
 		$notif = null;
-		//TODO: Will need to ensure non-concurrency when landing https://github.com/FreshRSS/FreshRSS/pull/3096
+		Minz_Session::lock();
 		$requests = Minz_Session::param('requests');
 		$requests = Minz_Session::param('requests');
 		if ($requests) {
 		if ($requests) {
 			//Delete abandonned notifications
 			//Delete abandonned notifications
@@ -301,6 +302,7 @@ class Minz_Request {
 			}
 			}
 			Minz_Session::_param('requests', $requests);
 			Minz_Session::_param('requests', $requests);
 		}
 		}
+		Minz_Session::unlock();
 		return $notif;
 		return $notif;
 	}
 	}
 
 

+ 58 - 2
lib/Minz/Session.php

@@ -4,18 +4,51 @@
  * La classe Session gère la session utilisateur
  * La classe Session gère la session utilisateur
  */
  */
 class Minz_Session {
 class Minz_Session {
+	private static $volatile = false;
+
+	/**
+	 * For mutual exclusion.
+	 */
+	private static $locked = false;
+
+	public static function lock() {
+		if (!self::$volatile && !self::$locked && session_start()) {
+			self::$locked = true;
+		}
+		return self::$locked;
+	}
+
+	public static function unlock() {
+		if (!self::$volatile && session_write_close()) {
+			self::$locked = false;
+		}
+		return self::$locked;
+	}
+
 	/**
 	/**
 	 * Initialise la session, avec un nom
 	 * Initialise la session, avec un nom
 	 * Le nom de session est utilisé comme nom pour les cookies et les URLs(i.e. PHPSESSID).
 	 * Le nom de session est utilisé comme nom pour les cookies et les URLs(i.e. PHPSESSID).
 	 * Il ne doit contenir que des caractères alphanumériques ; il doit être court et descriptif
 	 * Il ne doit contenir que des caractères alphanumériques ; il doit être court et descriptif
+	 * If the volatile parameter is true, then no cookie and not session storage are used.
+	 * Volatile is especially useful for API calls without cookie / Web session.
 	 */
 	 */
-	public static function init($name) {
+	public static function init($name, $volatile = false) {
+		self::$volatile = $volatile;
+		if (self::$volatile) {
+			$_SESSION = [];
+			return;
+		}
+
 		$cookie = session_get_cookie_params();
 		$cookie = session_get_cookie_params();
 		self::keepCookie($cookie['lifetime']);
 		self::keepCookie($cookie['lifetime']);
 
 
 		// démarre la session
 		// démarre la session
 		session_name($name);
 		session_name($name);
+		//When using cookies (default value), session_stars() sends HTTP headers
 		session_start();
 		session_start();
+		session_write_close();
+		//Use cookie only the first time the session is started to avoid resending HTTP headers
+		ini_set('session.use_cookies', '0');
 	}
 	}
 
 
 
 
@@ -35,13 +68,34 @@ class Minz_Session {
 	 * @param $v la valeur à attribuer, false pour supprimer
 	 * @param $v la valeur à attribuer, false pour supprimer
 	 */
 	 */
 	public static function _param($p, $v = false) {
 	public static function _param($p, $v = false) {
+		if (!self::$volatile && !self::$locked) {
+			session_start();
+		}
 		if ($v === false) {
 		if ($v === false) {
 			unset($_SESSION[$p]);
 			unset($_SESSION[$p]);
 		} else {
 		} else {
 			$_SESSION[$p] = $v;
 			$_SESSION[$p] = $v;
 		}
 		}
+		if (!self::$volatile && !self::$locked) {
+			session_write_close();
+		}
 	}
 	}
 
 
+	public static function _params($keyValues) {
+		if (!self::$volatile && !self::$locked) {
+			session_start();
+		}
+		foreach ($keyValues as $k => $v) {
+			if ($v === false) {
+				unset($_SESSION[$k]);
+			} else {
+				$_SESSION[$k] = $v;
+			}
+		}
+		if (!self::$volatile && !self::$locked) {
+			session_write_close();
+		}
+	}
 
 
 	/**
 	/**
 	 * Permet d'effacer une session
 	 * Permet d'effacer une session
@@ -50,7 +104,9 @@ class Minz_Session {
 	public static function unset_session($force = false) {
 	public static function unset_session($force = false) {
 		$language = self::param('language');
 		$language = self::param('language');
 
 
-		session_destroy();
+		if (!self::$volatile) {
+			session_destroy();
+		}
 		$_SESSION = array();
 		$_SESSION = array();
 
 
 		if (!$force) {
 		if (!$force) {

+ 1 - 3
p/api/fever.php

@@ -25,9 +25,7 @@ if (!FreshRSS_Context::$system_conf->api_enabled) {
 	die('Service Unavailable!');
 	die('Service Unavailable!');
 }
 }
 
 
-ini_set('session.use_cookies', '0');
-register_shutdown_function('session_destroy');
-Minz_Session::init('FreshRSS');
+Minz_Session::init('FreshRSS', true);
 // ================================================================================================
 // ================================================================================================
 
 
 // <Debug>
 // <Debug>

+ 1 - 3
p/api/greader.php

@@ -935,9 +935,7 @@ if (!FreshRSS_Context::$system_conf->api_enabled) {
 	checkCompatibility();
 	checkCompatibility();
 }
 }
 
 
-ini_set('session.use_cookies', '0');
-register_shutdown_function('session_destroy');
-Minz_Session::init('FreshRSS');
+Minz_Session::init('FreshRSS', true);
 
 
 $user = $pathInfos[1] === 'accounts' ? '' : authorizationToUser();
 $user = $pathInfos[1] === 'accounts' ? '' : authorizationToUser();
 FreshRSS_Context::$user_conf = null;
 FreshRSS_Context::$user_conf = null;

+ 3 - 2
p/i/index.php

@@ -27,8 +27,6 @@ if (file_exists(DATA_PATH . '/do-install.txt')) {
 	require(APP_PATH . '/install.php');
 	require(APP_PATH . '/install.php');
 } else {
 } else {
 	session_cache_limiter('');
 	session_cache_limiter('');
-	Minz_Session::init('FreshRSS');
-	Minz_Session::_param('keepAlive', 1);	//To prevent the PHP session from expiring
 
 
 	if (!file_exists(DATA_PATH . '/no-cache.txt')) {
 	if (!file_exists(DATA_PATH . '/no-cache.txt')) {
 		require(LIB_PATH . '/http-conditional.php');
 		require(LIB_PATH . '/http-conditional.php');
@@ -38,6 +36,8 @@ if (file_exists(DATA_PATH . '/do-install.txt')) {
 			@filemtime(join_path(DATA_PATH, 'config.php'))
 			@filemtime(join_path(DATA_PATH, 'config.php'))
 		);
 		);
 		if (httpConditional($dateLastModification, 0, 0, false, PHP_COMPRESSION, true)) {
 		if (httpConditional($dateLastModification, 0, 0, false, PHP_COMPRESSION, true)) {
+			Minz_Session::init('FreshRSS');
+			Minz_Session::_param('keepAlive', 1);	//To prevent the PHP session from expiring
 			exit();	//No need to send anything
 			exit();	//No need to send anything
 		}
 		}
 	}
 	}
@@ -65,6 +65,7 @@ if (file_exists(DATA_PATH . '/do-install.txt')) {
 		if ($result === true) {
 		if ($result === true) {
 			$front_controller = new FreshRSS();
 			$front_controller = new FreshRSS();
 			$front_controller->init();
 			$front_controller->init();
+			Minz_Session::_param('keepAlive', 1);	//To prevent the PHP session from expiring
 			$front_controller->run();
 			$front_controller->run();
 		} else {
 		} else {
 			$error = $result;
 			$error = $result;