From 4ee4f16ffe06e247d2cb79a2054ab5d5315d82b2 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 15 Dec 2013 11:24:14 +0100 Subject: Problème de casse renommage répertoire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Minz/FrontController.php | 97 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 lib/Minz/FrontController.php (limited to 'lib/Minz/FrontController.php') diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php new file mode 100644 index 000000000..eb9835fe5 --- /dev/null +++ b/lib/Minz/FrontController.php @@ -0,0 +1,97 @@ +. +# +# ***** END LICENSE BLOCK ***** + +/** + * La classe FrontController est le Dispatcher du framework, elle lance l'application + * Elle est appelée en général dans le fichier index.php à la racine du serveur + */ +class Minz_FrontController { + protected $dispatcher; + protected $router; + + /** + * Constructeur + * Initialise le router et le dispatcher + */ + public function __construct () { + if (LOG_PATH === false) { + $this->killApp ('Path doesn\'t exist : LOG_PATH'); + } + + try { + Minz_Configuration::init (); + + Minz_Request::init (); + + $this->router = new Minz_Router (); + $this->router->init (); + } catch (Minz_RouteNotFoundException $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + Minz_Error::error ( + 404, + array ('error' => array ($e->getMessage ())) + ); + } catch (Minz_Exception $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + $this->killApp ($e->getMessage ()); + } + + $this->dispatcher = Minz_Dispatcher::getInstance ($this->router); + } + + /** + * Démarre l'application (lance le dispatcher et renvoie la réponse + */ + public function run () { + try { + $this->dispatcher->run (); + Minz_Response::send (); + } catch (Minz_Exception $e) { + try { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + } catch (Minz_PermissionDeniedException $e) { + $this->killApp ($e->getMessage ()); + } + + if ($e instanceof Minz_FileNotExistException || + $e instanceof Minz_ControllerNotExistException || + $e instanceof Minz_ControllerNotActionControllerException || + $e instanceof Minz_ActionException) { + Minz_Error::error ( + 404, + array ('error' => array ($e->getMessage ())), + true + ); + } else { + $this->killApp (); + } + } + } + + /** + * Permet d'arrêter le programme en urgence + */ + private function killApp ($txt = '') { + if ($txt == '') { + $txt = 'See logs files'; + } + exit ('### Application problem ###
'."\n".$txt); + } +} -- cgit v1.2.3 From 385b5b1b40ee029f6f828c4c3076d2a6949d600c Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 21 Dec 2013 14:33:21 +0100 Subject: Install.php : permet d'être relancé sur une installation existante en chargeant les paramètres existants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contribue à https://github.com/marienfressinaud/FreshRSS/issues/255 Vérifier base_url et token, que je n'ai pas re-testés. --- README.md | 28 +++---- lib/Minz/FrontController.php | 2 +- public/install.php | 194 ++++++++++++++++++++++++------------------- 3 files changed, 124 insertions(+), 100 deletions(-) (limited to 'lib/Minz/FrontController.php') diff --git a/README.md b/README.md index d2ef8d92f..2eb9ca03a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # FreshRSS -FreshRSS est un agrégateur de flux RSS à auto-héberger à l'image de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/). Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable. +FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/). Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable. * Site officiel : http://marienfressinaud.github.io/FreshRSS/ * Démo : http://marienfressinaud.fr/projets/freshrss/ @@ -11,9 +11,9 @@ FreshRSS est un agrégateur de flux RSS à auto-héberger à l'image de [Leed](h ![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png) # Disclaimer -Cette application a été développée pour s'adapter à des besoins personnels et non professionels. +Cette application a été développée pour s’adapter à des besoins personnels et non professionels. Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement. -Je m'engage néanmoins à répondre dans la mesure du possible aux demandes d'évolution si celles-ci me semblent justifiées. +Je m’engage néanmoins à répondre dans la mesure du possible aux demandes d’évolution si celles-ci me semblent justifiées. Privilégiez pour cela des demandes sur GitHub (https://github.com/marienfressinaud/FreshRSS/issues) ou par mail (dev@marienfressinaud.fr) @@ -26,20 +26,20 @@ Privilégiez pour cela des demandes sur GitHub * Un navigateur Web récent tel Firefox, Chrome, Opera, Safari, Internet Explorer 9+ * Fonctionne aussi sur mobile -![Capture d'écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) +![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) # Installation -1. Récupérez l'application FreshRSS via la commande git ou [en téléchargeant l'archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip) -2. Déplacez l'application où vous voulez sur votre serveur (attention, la partie accessible se trouve dans le répertoire `./public`) -3. Accédez à FreshRSS à travers votre navigateur web et suivez les instructions d'installation -4. Tout devrait fonctionner :) En cas de problème, n'hésitez pas à me contacter. +1. Récupérez l’application FreshRSS via la commande git ou [en téléchargeant l’archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip) +2. Placez l’application sur votre serveur (la partie à exposer au Web est le répertoire `./public`) +3. Accédez à FreshRSS à travers votre navigateur Web et suivez les instructions d’installation +4. Tout devrait fonctionner :) En cas de problème, n’hésitez pas à me contacter. -# Sécurité et conseils -1. Pour une meilleure sécurité, faites en sorte que seul le répertoire `./public` soit accessible par le navigateur. Faites pointer un sous-domaine sur le répertoire `./public` par exemple -2. Dans tous les cas, assurez-vous que `./data/application.ini` ne puisse pas être téléchargé ! -3. Le fichier de log peut être utile à lire si vous avez des soucis -4. Le fichier `./public/index.php` défini les chemins d'accès aux répertoires clés de l'application. Si vous les bougez, tout se passe ici. -5. Vous pouvez ajouter une tâche CRON sur le script d'actualisation des flux. Il s'agit d'un script PHP à exécuter avec la commande `php`. Par exemple, pour exécuter le script toutes les heures : +# Conseils +1. Pour une meilleure sécurité, faites en sorte que seul le répertoire `./public` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./public`. +2. Les données sensibles se trouvent dans le répertoire `./data/` (déjà protégé par un .htaccess pour Apache - vérifiez que cela fonctionne -, à protéger vous-même dans le cas d’autres serveurs Web). +3. En cas de problème, les logs peuvent être utile à lire, soit depuis l’interface de FreshRSS, soit manuellement depuis `./data/log/*.log`. +4. Le fichier `./constants.php` définit les chemins d’accès aux répertoires clés de l’application. Si vous les bougez, tout se passe ici. +5. Vous pouvez ajouter une tâche CRON sur le script d’actualisation des flux. Il s’agit d’un script PHP à exécuter avec la commande `php`. Par exemple, pour exécuter le script toutes les heures : ``` 7 * * * * php /chemin/vers/freshrss/actualize_script.php >/dev/null 2>&1 ``` diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index eb9835fe5..8e9c511a6 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -32,7 +32,7 @@ class Minz_FrontController { */ public function __construct () { if (LOG_PATH === false) { - $this->killApp ('Path doesn\'t exist : LOG_PATH'); + $this->killApp ('Path doesn’t exist : LOG_PATH'); } try { diff --git a/public/install.php b/public/install.php index 026e9098c..8d8613e38 100644 --- a/public/install.php +++ b/public/install.php @@ -88,11 +88,7 @@ function initTranslate () { global $translates; global $actual; - $l = getBetterLanguage ('en'); - if (isset ($_SESSION['language'])) { - $l = $_SESSION['language']; - } - $actual = $l; + $actual = isset($_SESSION['language']) ? $_SESSION['language'] : getBetterLanguage('en'); $file = APP_PATH . '/i18n/' . $actual . '.php'; if (file_exists ($file)) { @@ -148,10 +144,7 @@ function saveStep2 () { return false; } - $_SESSION['sel'] = md5 ( - uniqid (mt_rand (), true).implode ('', stat (__FILE__)) - ); - $_SESSION['base_url'] = addslashes ($_POST['base_url']); + $_SESSION['sel_application'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); $_SESSION['title'] = addslashes ($_POST['title']); $_SESSION['old_entries'] = $_POST['old_entries']; if (!is_int (intval ($_SESSION['old_entries'])) || @@ -163,8 +156,7 @@ function saveStep2 () { $token = ''; if ($_SESSION['mail_login']) { - $token = small_hash (time () . $_SESSION['sel']) - . small_hash ($_SESSION['base_url'] . $_SESSION['sel']); + $token = sha1($_SESSION['sel_application'] . $_SESSION['mail_login']); } $file_data = DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php'; @@ -196,25 +188,25 @@ function saveStep3 () { $_SESSION['bd_type'] = isset ($_POST['type']) ? $_POST['type'] : 'mysql'; $_SESSION['bd_host'] = addslashes ($_POST['host']); $_SESSION['bd_user'] = addslashes ($_POST['user']); - $_SESSION['bd_pass'] = addslashes ($_POST['pass']); - $_SESSION['bd_name'] = addslashes ($_POST['base']); + $_SESSION['bd_password'] = addslashes ($_POST['pass']); + $_SESSION['bd_base'] = addslashes ($_POST['base']); $_SESSION['bd_prefix'] = addslashes ($_POST['prefix']); $file_conf = DATA_PATH . '/application.ini'; $f = fopen ($file_conf, 'w'); writeLine ($f, '[general]'); - writeLine ($f, 'environment = "production"'); + writeLine ($f, 'environment = "' . empty($_SESSION['environment']) ? 'production' : $_SESSION['environment'] . '"'); writeLine ($f, 'use_url_rewriting = false'); - writeLine ($f, 'sel_application = "' . $_SESSION['sel'] . '"'); - writeLine ($f, 'base_url = "' . $_SESSION['base_url'] . '"'); + writeLine ($f, 'sel_application = "' . $_SESSION['sel_application'] . '"'); + writeLine ($f, 'base_url = ""'); writeLine ($f, 'title = "' . $_SESSION['title'] . '"'); writeLine ($f, 'default_user = "' . $_SESSION['default_user'] . '"'); writeLine ($f, '[db]'); writeLine ($f, 'type = "' . $_SESSION['bd_type'] . '"'); writeLine ($f, 'host = "' . $_SESSION['bd_host'] . '"'); writeLine ($f, 'user = "' . $_SESSION['bd_user'] . '"'); - writeLine ($f, 'password = "' . $_SESSION['bd_pass'] . '"'); - writeLine ($f, 'base = "' . $_SESSION['bd_name'] . '"'); + writeLine ($f, 'password = "' . $_SESSION['bd_password'] . '"'); + writeLine ($f, 'base = "' . $_SESSION['bd_base'] . '"'); writeLine ($f, 'prefix = "' . $_SESSION['bd_prefix'] . '"'); fclose ($f); @@ -237,6 +229,50 @@ function deleteInstall () { } } +function moveOldFiles() { + $mvs = array( + '/app/configuration/application.ini' => '/data/application.ini', //v0.6 + '/public/data/Configuration.array.php' => '/data/Configuration.array.php', //v0.6 + ); + $ok = true; + foreach ($mvs as $fFrom => $fTo) { + if (file_exists(FRESHRSS_PATH . $fFrom)) { + if (copy(FRESHRSS_PATH . $fFrom, FRESHRSS_PATH . $fTo)) { + @unlink(FRESHRSS_PATH . $fFrom); + } else { + $ok = false; + } + } + } + return $ok; +} + +function delTree($dir) { //http://php.net/rmdir#110489 + if (!is_dir($dir)) { + return true; + } + $files = array_diff(scandir($dir), array('.', '..')); + foreach ($files as $file) { + $f = $dir . '/' . $file; + if (is_dir($f)) { + @chmod($f, 0777); + delTree($f); + } + else unlink($f); + } + return rmdir($dir); +} + +function removeOldFiles() { + $oldDirs = array('/app/configuration/', '/cache/', '/log/', '/public/data/', '/public/themes/printer/'); //v0.6 + + $ok = true; + foreach ($oldDirs as $oldDir) { + $ok &= delTree(FRESHRSS_PATH . $oldDir); + } + return $ok; +} + /*** VÉRIFICATIONS ***/ function checkStep () { $s0 = checkStep0 (); @@ -254,6 +290,47 @@ function checkStep () { } } function checkStep0 () { + moveOldFiles() && removeOldFiles(); + + if (file_exists(DATA_PATH . '/application.ini')) { + $ini_array = parse_ini_file(DATA_PATH . '/application.ini', true); + if ($ini_array) { + $ini_general = isset($ini_array['general']) ? $ini_array['general'] : null; + if ($ini_general) { + $keys = array('environment', 'sel_application', 'title', 'default_user'); + foreach ($keys as $key) { + if ((empty($_SESSION[$key])) && isset($ini_general[$key])) { + $_SESSION[$key] = $ini_general[$key]; + } + } + } + $ini_db = isset($ini_array['db']) ? $ini_array['db'] : null; + if ($ini_db) { + $keys = array('type', 'host', 'user', 'password', 'base', 'prefix'); + foreach ($keys as $key) { + if ((!isset($_SESSION['bd_' . $key])) && isset($ini_db[$key])) { + $_SESSION['bd_' . $key] = $ini_db[$key]; + } + } + } + } + } + + if (isset($_SESSION['default_user']) && file_exists(DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php')) { + $userConfig = include(DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php'); + } elseif (file_exists(DATA_PATH . '/Configuration.array.php')) { + $userConfig = include(DATA_PATH . '/Configuration.array.php'); //v0.6 + } else { + $userConfig = array(); + } + + $keys = array('language', 'old_entries', 'mail_login'); + foreach ($keys as $key) { + if ((!isset($_SESSION[$key])) && isset($userConfig[$key])) { + $_SESSION[$key] = $userConfig[$key]; + } + } + $languages = availableLanguages (); $language = isset ($_SESSION['language']) && isset ($languages[$_SESSION['language']]); @@ -288,55 +365,8 @@ function checkStep1 () { ); } -function moveOldFiles() { - $mvs = array( - '/app/configuration/application.ini' => '/data/application.ini', - '/public/data/Configuration.array.php' => '/data/Configuration.array.php', - ); - $ok = true; - foreach ($mvs as $fFrom => $fTo) { - if (file_exists(FRESHRSS_PATH . $fFrom)) { - if (copy(FRESHRSS_PATH . $fFrom, FRESHRSS_PATH . $fTo)) { - @unlink(FRESHRSS_PATH . $fFrom); - } else { - $ok = false; - } - } - } - return $ok; -} - -function delTree($dir) { //http://php.net/rmdir#110489 - if (!is_dir($dir)) { - return true; - } - $files = array_diff(scandir($dir), array('.', '..')); - foreach ($files as $file) { - $f = $dir . '/' . $file; - if (is_dir($f)) { - @chmod($f, 0777); - delTree($f); - } - else unlink($f); - } - return rmdir($dir); -} - -function removeOldFiles() { - $oldDirs = array('/app/configuration/', '/cache/', '/log/', '/public/data/', '/public/themes/printer/'); - - $ok = true; - foreach ($oldDirs as $oldDir) { - $ok &= delTree(FRESHRSS_PATH . $oldDir); - } - return $ok; -} - function checkStep2 () { - moveOldFiles() && removeOldFiles(); - - $conf = isset ($_SESSION['sel']) && - isset ($_SESSION['base_url']) && + $conf = isset ($_SESSION['sel_application']) && isset ($_SESSION['title']) && isset ($_SESSION['old_entries']) && isset ($_SESSION['mail_login']) && @@ -358,11 +388,13 @@ function checkStep2 () { } function checkStep3 () { $conf = file_exists (DATA_PATH . '/application.ini'); + $bd = isset ($_SESSION['bd_type']) && isset ($_SESSION['bd_host']) && isset ($_SESSION['bd_user']) && - isset ($_SESSION['bd_pass']) && - isset ($_SESSION['bd_name']); + isset ($_SESSION['bd_password']) && + isset ($_SESSION['bd_base']) && + isset ($_SESSION['bd_prefix']); $conn = !isset ($_SESSION['bd_error']) || !$_SESSION['bd_error']; return array ( @@ -387,21 +419,21 @@ function checkBD () { $str = 'mysql:host=' . $_SESSION['bd_host'] . ';'; $c = new PDO ($str, $_SESSION['bd_user'], - $_SESSION['bd_pass'], + $_SESSION['bd_password'], $driver_options); - $sql = sprintf (SQL_REQ_CREATE_DB, $_SESSION['bd_name']); + $sql = sprintf (SQL_REQ_CREATE_DB, $_SESSION['bd_base']); $res = $c->query ($sql); // on écrase la précédente connexion en sélectionnant la nouvelle BDD - $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_name']; + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; } elseif($_SESSION['bd_type'] == 'sqlite') { - $str = 'sqlite:' . DATA_PATH . $_SESSION['bd_name'] . '.sqlite'; + $str = 'sqlite:' . DATA_PATH . $_SESSION['bd_base'] . '.sqlite'; } $c = new PDO ($str, $_SESSION['bd_user'], - $_SESSION['bd_pass'], + $_SESSION['bd_password'], $driver_options); $sql = sprintf (SQL_REQ_CAT, $_SESSION['bd_prefix_user']); @@ -549,13 +581,6 @@ function printStep2 () { -
@@ -643,14 +668,14 @@ function printStep3 () {
- +
- +
@@ -687,10 +712,10 @@ function printStep5 () { - -- cgit v1.2.3 From b4c477ca41a7ecaa6364dd6a97603829b14b11ef Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 2 Jan 2014 01:47:03 +0100 Subject: actualize_script compatible multi-utilisateur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Messages plus verbeux dans actualize_script * Ajout d'un message syslog lorsque SimplePie fait une requête HTTP * Minz_Session ne fermait pas les sessions complètement. * Nouvelle option dans Minz_Dispatcher et Minz_FrontController pour ne pas utiliser ob_gzhandler. Contribue à https://github.com/marienfressinaud/FreshRSS/issues/126 --- app/actualize_script.php | 36 ++++++++++++++++++++++++++---------- app/views/feed/actualize.phtml | 2 +- lib/Minz/Dispatcher.php | 23 +++++++++++++++++------ lib/Minz/FrontController.php | 15 ++++++++++++++- lib/Minz/ModelPdo.php | 5 +++++ lib/Minz/Session.php | 2 +- lib/SimplePie/SimplePie/File.php | 1 + 7 files changed, 65 insertions(+), 19 deletions(-) (limited to 'lib/Minz/FrontController.php') diff --git a/app/actualize_script.php b/app/actualize_script.php index efe21fab6..e0f995afe 100755 --- a/app/actualize_script.php +++ b/app/actualize_script.php @@ -3,24 +3,40 @@ require(dirname(__FILE__) . '/../constants.php'); //TODO: check if already running -$_GET['c'] = 'feed'; -$_GET['a'] = 'actualize'; -$_GET['force'] = true; -$_SERVER['HTTP_HOST'] = ''; - require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader -$freshRSS = new FreshRSS (); +session_cache_limiter(''); +ob_implicit_flush(false); +ob_start(); +echo 'Results: ', "\n"; //Buffered $users = listUsers(); shuffle($users); -foreach ($users as $user) { +foreach ($users as $myUser) { + syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser); + fwrite(STDOUT, 'Actualize ' . $myUser . "...\n"); //Unbuffered + echo $myUser, ' '; //Buffered + + $_GET['c'] = 'feed'; + $_GET['a'] = 'actualize'; + $_GET['ajax'] = 1; + $_GET['force'] = true; + $_SERVER['HTTP_HOST'] = ''; + + $freshRSS = new FreshRSS(); + $freshRSS->_useOb(false); + Minz_Session::init('FreshRSS'); - Minz_Session::_param('currentUser', $user); + Minz_Session::_param('currentUser', $myUser); + $freshRSS->init(); $freshRSS->run(); - //invalidateHttpCache(); - touch(LOG_PATH . '/' . $user . '.log'); + + invalidateHttpCache(); Minz_Session::unset_session(true); + Minz_ModelPdo::clean(); } +syslog(LOG_INFO, 'FreshRSS actualize done.'); +ob_end_flush(); +fwrite(STDOUT, 'Done.' . "\n"); diff --git a/app/views/feed/actualize.phtml b/app/views/feed/actualize.phtml index a0aba9318..d86bac9de 100644 --- a/app/views/feed/actualize.phtml +++ b/app/views/feed/actualize.phtml @@ -1 +1 @@ -OK \ No newline at end of file +OK diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php index c2c5e7f65..71dfe8ac6 100644 --- a/lib/Minz/Dispatcher.php +++ b/lib/Minz/Dispatcher.php @@ -40,19 +40,26 @@ class Minz_Dispatcher { * Remplit le body de Response à partir de la Vue * @exception Minz_Exception */ - public function run () { + public function run ($ob = true) { $cache = new Minz_Cache(); // Le ob_start est dupliqué : sans ça il y a un bug sous Firefox // ici on l'appelle avec 'ob_gzhandler', après sans. // Vraisemblablement la compression fonctionne mais c'est sale // J'ignore les effets de bord :( - ob_start ('ob_gzhandler'); + if ($ob) { + ob_start ('ob_gzhandler'); + } if (Minz_Cache::isEnabled () && !$cache->expired ()) { - ob_start (); + if ($ob) { + ob_start (); + } $cache->render (); - $text = ob_get_clean(); + if ($ob) { + $text = ob_get_clean(); + } } else { + $text = ''; //TODO: Clean this code while (Minz_Request::$reseted) { Minz_Request::$reseted = false; @@ -67,9 +74,13 @@ class Minz_Dispatcher { $this->controller->lastAction (); if (!Minz_Request::$reseted) { - ob_start (); + if ($ob) { + ob_start (); + } $this->controller->view ()->build (); - $text = ob_get_clean(); + if ($ob) { + $text = ob_get_clean(); + } } } catch (Minz_Exception $e) { throw $e; diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index 8e9c511a6..7b8526bc8 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -26,6 +26,8 @@ class Minz_FrontController { protected $dispatcher; protected $router; + private $useOb = true; + /** * Constructeur * Initialise le router et le dispatcher @@ -61,7 +63,7 @@ class Minz_FrontController { */ public function run () { try { - $this->dispatcher->run (); + $this->dispatcher->run ($this->useOb); Minz_Response::send (); } catch (Minz_Exception $e) { try { @@ -94,4 +96,15 @@ class Minz_FrontController { } exit ('### Application problem ###
'."\n".$txt); } + + public function useOb() { + return $this->useOb; + } + + /** + * Use ob_start('ob_gzhandler') or not. + */ + public function _useOb($ob) { + return $this->useOb = (bool)$ob; + } } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index a643da1f0..831df13a2 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -93,6 +93,11 @@ class Minz_ModelPdo { $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } + + public static function clean() { + self::$sharedBd = null; + self::$sharedPrefix = ''; + } } class FreshPDO extends PDO { diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php index 3f6ed88a3..37faff0fb 100644 --- a/lib/Minz/Session.php +++ b/lib/Minz/Session.php @@ -60,7 +60,7 @@ class Minz_Session { public static function unset_session ($force = false) { $language = self::param ('language'); - session_unset (); + session_destroy(); self::$session = array (); if (!$force) { diff --git a/lib/SimplePie/SimplePie/File.php b/lib/SimplePie/SimplePie/File.php index 063ad955e..cf926cf5a 100644 --- a/lib/SimplePie/SimplePie/File.php +++ b/lib/SimplePie/SimplePie/File.php @@ -77,6 +77,7 @@ class SimplePie_File $this->useragent = $useragent; if (preg_match('/^http(s)?:\/\//i', $url)) { + syslog(LOG_INFO, 'SimplePie GET ' . $url); //FreshRSS if ($useragent === null) { $useragent = ini_get('user_agent'); -- cgit v1.2.3 From d58886a937cbe425163526fc2ba3d2a118602035 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 12 Jan 2014 03:10:31 +0100 Subject: Implémentation de l'indentification par mot de passe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implémentation de https://github.com/marienfressinaud/FreshRSS/issues/104 --- CHANGELOG | 3 ++ app/Controllers/indexController.php | 52 +++++++++++++++++++++++++++-- app/Controllers/javascriptController.php | 12 +++---- app/Controllers/usersController.php | 26 ++++++++++++--- app/FreshRSS.php | 27 ++++++++++++--- app/i18n/en.php | 7 ++-- app/i18n/fr.php | 7 ++-- app/layout/header.phtml | 51 +++++++++++++++++++--------- app/views/configure/users.phtml | 16 ++++++--- app/views/helpers/javascript_vars.phtml | 4 +-- app/views/helpers/view/login.phtml | 43 ++++++++++++++++++++++++ app/views/index/index.phtml | 14 ++------ lib/Minz/Configuration.php | 3 +- lib/Minz/FrontController.php | 2 +- p/scripts/main.js | 57 ++++++++++++++++++++++++++++++-- 15 files changed, 265 insertions(+), 59 deletions(-) create mode 100644 app/views/helpers/view/login.phtml (limited to 'lib/Minz/FrontController.php') diff --git a/CHANGELOG b/CHANGELOG index fe856fe4a..5c9b56465 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,8 @@ * Nouveau mode multi-utilisateur * L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs * Nécessite un contrôle d’accès, soit : + * par le nouveau mode de connexion par formulaire (nom d’utilisateur + mot de passe) + * relativement sûr même sans HTTPS (le mot de passe n’est pas transmis en clair) * par HTTP (par exemple sous Apache en créant un fichier ./p/i/.htaccess et .htpasswd) * le nom d’utilisateur HTTP doit correspondre au nom d’utilisateur FreshRSS * par Mozilla Persona, en renseignant l’adresse courriel des utilisateurs @@ -68,6 +70,7 @@ * Réorganisation des fichiers et répertoires, en particulier : * Tous les fichiers utilisateur sont dans “./data/” (y compris “cache”, “favicons”, et “log”) * Déplacement de “./app/configuration/application.ini” vers “./data/config.php” + * Meilleure sécurité et compatibilité * Déplacement de “./public/data/Configuration.array.php” vers “./data/*_user.php” * Déplacement de “./public/” vers “./p/” * Déplacement de “./public/index.php” vers “./p/i/index.php” (voir cookie ci-dessous) diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 81dfefabb..772a08f30 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -47,8 +47,6 @@ class FreshRSS_index_Controller extends Minz_ActionController { $this->view->_useLayout (false); header('Content-Type: application/rss+xml; charset=utf-8'); } else { - Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); - if ($output === 'global') { Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); } @@ -290,8 +288,56 @@ class FreshRSS_index_Controller extends Minz_ActionController { } public function logoutAction () { + $this->view->_useLayout(false); + invalidateHttpCache(); + Minz_Session::_param('currentUser'); + Minz_Session::_param('mail'); + Minz_Session::_param('passwordHash'); + } + + public function formLoginAction () { $this->view->_useLayout (false); - Minz_Session::_param ('mail'); + if (Minz_Request::isPost()) { + $ok = false; + $nonce = Minz_Session::param('nonce'); + $username = Minz_Request::param('username', ''); + $c = Minz_Request::param('challenge', ''); + if (ctype_alnum($username) && ctype_graph($c) && ctype_alnum($nonce)) { + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($username); + $s = $conf->passwordHash; + $ok = password_verify($nonce . $s, $c); + if ($ok) { + Minz_Session::_param('currentUser', $username); + Minz_Session::_param('passwordHash', $s); + } else { + Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); + } + } catch (Minz_Exception $me) { + Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); + } + } + if (!$ok) { + $notif = array( + 'type' => 'bad', + 'content' => Minz_Translate::t('invalid_login') + ); + Minz_Session::_param('notification', $notif); + } + } + invalidateHttpCache(); + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + + public function formLogoutAction () { + $this->view->_useLayout(false); invalidateHttpCache(); + Minz_Session::_param('currentUser'); + Minz_Session::_param('mail'); + Minz_Session::_param('passwordHash'); + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } } diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php index e29f439d8..02e424437 100755 --- a/app/Controllers/javascriptController.php +++ b/app/Controllers/javascriptController.php @@ -17,7 +17,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController { $this->view->categories = $catDAO->listCategories(true, false); } - // For Web-form login + //For Web-form login public function nonceAction() { header('Content-Type: application/json; charset=UTF-8'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T')); @@ -29,15 +29,15 @@ class FreshRSS_javascript_Controller extends Minz_ActionController { if (ctype_alnum($user)) { try { $conf = new FreshRSS_Configuration($user); - $hash = $conf->passwordHash; //CRYPT_BLOWFISH - Blowfish hashing with a salt as follows: "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". - if (strlen($hash) >= 60) { - $this->view->salt1 = substr($hash, 0, 29); + $s = $conf->passwordHash; + if (strlen($s) >= 60) { + $this->view->salt1 = substr($s, 0, 29); //CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". $this->view->nonce = sha1(Minz_Configuration::salt() . uniqid(mt_rand(), true)); - Minz_Session::_param ('nonce', $this->view->nonce); + Minz_Session::_param('nonce', $this->view->nonce); return; //Success } } catch (Minz_Exception $me) { - Minz_Log::record ('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); + Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); } } $this->view->nonce = ''; //Failure diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index 8954c845d..cb5ebd209 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -21,18 +21,20 @@ class FreshRSS_users_Controller extends Minz_ActionController { if (!function_exists('password_hash')) { include_once(LIB_PATH . '/password_compat.php'); } - $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT); //A bit expensive, on purpose + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => 8)); //This will also have to be computed client side on mobile devices, so do not use a too high cost $passwordPlain = ''; + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js $this->view->conf->_passwordHash($passwordHash); } - $mail = Minz_Request::param('mail_login', false); - $this->view->conf->_mail_login($mail); + $email = Minz_Request::param('mail_login', false); + $this->view->conf->_mail_login($email); $ok &= $this->view->conf->save(); $email = $this->view->conf->mail_login; Minz_Session::_param('mail', $email); + Minz_Session::_param('passwordHash', $this->view->conf->passwordHash); if ($email != '') { $personaFile = DATA_PATH . '/persona/' . $email . '.txt'; @@ -89,10 +91,25 @@ class FreshRSS_users_Controller extends Minz_ActionController { $ok &= !file_exists($configPath); } if ($ok) { + + $passwordPlain = Minz_Request::param('new_user_passwordPlain', false); + $passwordHash = ''; + if ($passwordPlain != '') { + Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP + $_POST['new_user_passwordPlain'] = ''; + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => 8)); + $passwordPlain = ''; + } + if (empty($passwordHash)) { + $passwordHash = ''; + } + $new_user_email = filter_var($_POST['new_user_email'], FILTER_VALIDATE_EMAIL); if (empty($new_user_email)) { $new_user_email = ''; - $ok &= Minz_Configuration::authType() !== 'persona'; } else { $personaFile = DATA_PATH . '/persona/' . $new_user_email . '.txt'; @unlink($personaFile); @@ -102,6 +119,7 @@ class FreshRSS_users_Controller extends Minz_ActionController { if ($ok) { $config_array = array( 'language' => $new_user_language, + 'passwordHash' => $passwordHash, 'mail_login' => $new_user_email, ); $ok &= (file_put_contents($configPath, "accessControl(Minz_Session::param('currentUser', '')); + $loginOk = $this->accessControl(Minz_Session::param('currentUser', '')); $this->loadParamsView(); - $this->loadStylesAndScripts(); //TODO: Do not load that when not needed, e.g. some Ajax requests + $this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests $this->loadNotifications(); } private function accessControl($currentUser) { if ($currentUser == '') { switch (Minz_Configuration::authType()) { + case 'form': + $currentUser = Minz_Configuration::defaultUser(); + Minz_Session::_param('passwordHash'); + $loginOk = false; + break; case 'http_auth': $currentUser = httpAuthUser(); $loginOk = $currentUser != ''; @@ -73,6 +78,9 @@ class FreshRSS extends Minz_FrontController { if ($loginOk) { switch (Minz_Configuration::authType()) { + case 'form': + $loginOk = Minz_Session::param('passwordHash') === $this->conf->passwordHash; + break; case 'http_auth': $loginOk = strcasecmp($currentUser, httpAuthUser()) === 0; break; @@ -92,6 +100,7 @@ class FreshRSS extends Minz_FrontController { } } Minz_View::_param ('loginOk', $loginOk); + return $loginOk; } private function loadParamsView () { @@ -104,7 +113,7 @@ class FreshRSS extends Minz_FrontController { } } - private function loadStylesAndScripts () { + private function loadStylesAndScripts ($loginOk) { $theme = FreshRSS_Themes::get_infos($this->conf->theme); if ($theme) { foreach($theme['files'] as $file) { @@ -112,14 +121,22 @@ class FreshRSS extends Minz_FrontController { } } - if (Minz_Configuration::authType() === 'persona') { - Minz_View::appendScript ('https://login.persona.org/include.js'); + switch (Minz_Configuration::authType()) { + case 'form': + if (!$loginOk) { + Minz_View::appendScript(Minz_Url::display ('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'))); + } + break; + case 'persona': + Minz_View::appendScript('https://login.persona.org/include.js'); + break; } $includeLazyLoad = $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param ('output') === 'reader'); Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')), false, !$includeLazyLoad, !$includeLazyLoad); if ($includeLazyLoad) { Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.lazyload.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.lazyload.min.js'))); } + Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); Minz_View::appendScript (Minz_Url::display ('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); } diff --git a/app/i18n/en.php b/app/i18n/en.php index 3b9936e8e..71ca9538f 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -170,6 +170,9 @@ return array ( 'is_admin' => 'is administrator', 'auth_type' => 'Authentication method', 'auth_none' => 'None (dangerous)', + 'auth_form' => 'Web form (traditional, requires JavaScript)', + 'http_auth' => 'HTTP (for advanced users with HTTPS)', + 'auth_persona' => 'Mozilla Persona (modern, requires JavaScript)', 'users_list' => 'List of users', 'create_user' => 'Create new user', 'username' => 'Username', @@ -258,8 +261,8 @@ return array ( 'logs_empty' => 'Log file is empty', 'clear_logs' => 'Clear the logs', - 'forbidden_access' => 'Forbidden access', - 'forbidden_access_description' => 'Access is password protected, please to read your feeds.', + 'forbidden_access' => 'Access forbidden! (%s)', + 'login_required' => 'Login required:', 'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 7e71cbb6d..8ffc5ec88 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -170,6 +170,9 @@ return array ( 'is_admin' => 'est administrateur', 'auth_type' => 'Méthode d’authentification', 'auth_none' => 'Aucune (dangereux)', + 'auth_form' => 'Formulaire (traditionnel, requiert JavaScript)', + 'http_auth' => 'HTTP (pour utilisateurs avancés avec HTTPS)', + 'auth_persona' => 'Mozilla Persona (moderne, requiert JavaScript)', 'users_list' => 'Liste des utilisateurs', 'create_user' => 'Créer un nouvel utilisateur', 'username' => 'Nom d’utilisateur', @@ -258,8 +261,8 @@ return array ( 'logs_empty' => 'Les logs sont vides', 'clear_logs' => 'Effacer les logs', - 'forbidden_access' => 'Accès interdit', - 'forbidden_access_description' => 'L’accès est protégé par un mot de passe, veuillez pour accéder aux flux.', + 'forbidden_access' => 'Accès interdit ! (%s)', + 'login_required' => 'Accès protégé par mot de passe :', 'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !', diff --git a/app/layout/header.phtml b/app/layout/header.phtml index 0f2c524c4..e90da6196 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -1,12 +1,25 @@ - - - +
@@ -62,16 +75,24 @@
  • - -
  • -
  • - +
  • - +
    - +
    diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index 68111bdbe..1597004e1 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -20,7 +20,7 @@
    - +
    @@ -52,11 +52,11 @@
    - $_SERVER['REMOTE_USER'] = ``
    @@ -141,6 +141,14 @@ +
    + +
    + + +
    +
    +
    conf->mail_login; ?> diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 92c068f7e..935294e60 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -30,8 +30,8 @@ if ($mail != 'null') { $mail = '"' . $mail . '"'; } - echo 'use_persona=', Minz_Configuration::authType() === 'persona' ? 'true' : 'false', - ',url_freshrss="', _url ('index', 'index'), '",', + echo 'authType="', Minz_Configuration::authType(), '",', + 'url_freshrss="', _url ('index', 'index'), '",', 'url_login="', _url ('index', 'login'), '",', 'url_logout="', _url ('index', 'logout'), '",', 'current_user_mail=', $mail, ",\n"; diff --git a/app/views/helpers/view/login.phtml b/app/views/helpers/view/login.phtml new file mode 100644 index 000000000..e4a24f9ed --- /dev/null +++ b/app/views/helpers/view/login.phtml @@ -0,0 +1,43 @@ +
    + +

    +
    + +
    + +
    +
    +
    + +
    + + + +
    +
    +
    +
    + +
    +
    +

    FreshRSS

    +

    + +

    +
    diff --git a/app/views/index/index.phtml b/app/views/index/index.phtml index 549d0b61e..9b69233e9 100644 --- a/app/views/index/index.phtml +++ b/app/views/index/index.phtml @@ -1,15 +1,5 @@
    -

    -

    -

    -
    loginOk || Minz_Configuration::allowAnonymous()) { @@ -31,8 +21,8 @@ if ($this->loginOk || Minz_Configuration::allowAnonymous()) { if ($token_is_ok) { $this->renderHelper ('view/rss_view'); } else { - showForbidden(); + $this->renderHelper ('view/login'); } } else { - showForbidden(); + $this->renderHelper ('view/login'); } diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 2c30661ed..433992e0d 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -109,7 +109,7 @@ class Minz_Configuration { return self::$auth_type !== 'none'; } public static function canLogIn() { - return self::$auth_type === 'persona'; + return self::$auth_type === 'form' || self::$auth_type === 'persona'; } public static function _allowAnonymous($allow = false) { @@ -118,6 +118,7 @@ class Minz_Configuration { public static function _authType($value) { $value = strtolower($value); switch ($value) { + case 'form': case 'http_auth': case 'persona': case 'none': diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index 7b8526bc8..80eda8877 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -34,7 +34,7 @@ class Minz_FrontController { */ public function __construct () { if (LOG_PATH === false) { - $this->killApp ('Path doesn’t exist : LOG_PATH'); + $this->killApp ('Path not found: LOG_PATH'); } try { diff --git a/p/scripts/main.js b/p/scripts/main.js index 24af1b210..0c4c3f1b2 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -587,6 +587,54 @@ function init_load_more(box) { } // +// +function poormanSalt() { //If crypto.getRandomValues is not available + var text = '$2a$04$', + base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; + for (var i = 22; i > 0; i--) { + text += base.charAt(Math.floor(Math.random() * 64)); + } + return text; +} + +function init_loginForm() { + var $loginForm = $('#loginForm'); + if ($loginForm.length === 0) { + return; + } + if (!(window.dcodeIO)) { + if (window.console) { + console.log('FreshRSS waiting for bcrypt.js…'); + } + window.setTimeout(init_loginForm, 100); + return; + } + $loginForm.on('submit', function() { + $('#loginButton').attr('disabled', ''); + var success = false; + $.ajax({ + url: './?c=javascript&a=nonce&user=' + $('#username').val(), + dataType: 'json', + async: false + }).done(function (data) { + if (data.salt1 == '' || data.nonce == '') { + alert('Invalid user!'); + } else { + var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), + s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1), + c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? 4 : poormanSalt()); + $('#challenge').val(c); + success = true; + } + }).fail(function() { + alert('Communication error!'); + }); + $('#loginButton').removeAttr('disabled'); + return success; + }); +} +// + // function init_persona() { if (!(navigator.id)) { @@ -696,8 +744,13 @@ function init_all() { init_notifications(); init_actualize(); init_load_more($stream); - if (use_persona) { - init_persona(); + switch (authType) { + case 'form': + init_loginForm(); + break; + case 'persona': + init_persona(); + break; } init_confirm_action(); init_print_action(); -- cgit v1.2.3