diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php new file mode 100644 index 000000000..2b67e34b8 --- /dev/null +++ b/app/Controllers/authController.php @@ -0,0 +1,182 @@ + 'index', 'a' => 'index'), true); + } + + $auth_type = Minz_Configuration::authType(); + switch ($auth_type) { + case 'form': + Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin')); + break; + case 'persona': + Minz_Request::forward(array('c' => 'auth', 'a' => 'personaLogin')); + break; + case 'http_auth': + case 'none': + // It should not happened! + Minz_Error::error(404); + default: + // TODO load plugin instead + Minz_Error::error(404); + } + } + + /** + * This action handles form login page. + * + * If this action is reached through a POST request, username and password + * are compared to login the current user. + * + * Parameters are: + * - nonce (default: false) + * - username (default: '') + * - challenge (default: '') + * - keep_logged_in (default: false) + */ + public function formLoginAction() { + invalidateHttpCache(); + + $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'); + Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime)); + + if (Minz_Request::isPost()) { + $nonce = Minz_Session::param('nonce'); + $username = Minz_Request::param('username', ''); + $challenge = Minz_Request::param('challenge', ''); + try { + $conf = new FreshRSS_Configuration($username); + } catch(Minz_Exception $e) { + // $username is not a valid user, nor the configuration file! + Minz_Log::warning('Login failure: ' . $e->getMessage()); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'auth', 'a' => 'login')); + } + + $ok = FreshRSS_FormAuth::checkCredentials( + $username, $conf->passwordHash, $nonce, $challenge + ); + if ($ok) { + // Set session parameter to give access to the user. + Minz_Session::_param('currentUser', $username); + Minz_Session::_param('passwordHash', $conf->passwordHash); + FreshRSS_Auth::giveAccess(); + + // Set cookie parameter if nedded. + if (Minz_Request::param('keep_logged_in')) { + FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash); + } else { + FreshRSS_FormAuth::deleteCookie(); + } + + // All is good, go back to the index. + Minz_Request::good(_t('login'), + array('c' => 'index', 'a' => 'index')); + } else { + Minz_Log::warning('Password mismatch for' . + ' user=' . $username . + ', nonce=' . $nonce . + ', c=' . $challenge); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'auth', 'a' => 'login')); + } + } + } + + /** + * This action handles Persona login page. + * + * If this action is reached through a POST request, assertion from Persona + * is verificated and user connected if all is ok. + * + * Parameter is: + * - assertion (default: false) + * + * @todo: Persona system should be moved to a plugin + */ + public function personaLoginAction() { + $this->view->res = false; + + if (Minz_Request::isPost()) { + $this->view->_useLayout(false); + + $assert = Minz_Request::param('assertion'); + $url = 'https://verifier.login.persona.org/verify'; + $params = 'assertion=' . $assert . '&audience=' . + urlencode(Minz_Url::display(null, 'php', true)); + $ch = curl_init(); + $options = array( + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_POST => 2, + CURLOPT_POSTFIELDS => $params + ); + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + curl_close($ch); + + $res = json_decode($result, true); + + $login_ok = false; + $reason = ''; + if ($res['status'] === 'okay') { + $email = filter_var($res['email'], FILTER_VALIDATE_EMAIL); + if ($email != '') { + $persona_file = DATA_PATH . '/persona/' . $email . '.txt'; + if (($current_user = @file_get_contents($persona_file)) !== false) { + $current_user = trim($current_user); + try { + $conf = new FreshRSS_Configuration($current_user); + $login_ok = strcasecmp($email, $conf->mail_login) === 0; + } catch (Minz_Exception $e) { + //Permission denied or conf file does not exist + $reason = 'Invalid configuration for user ' . + '[' . $current_user . '] ' . $e->getMessage(); + } + } + } else { + $reason = 'Invalid email format [' . $res['email'] . ']'; + } + } else { + $reason = $res['reason']; + } + + if ($login_ok) { + Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('mail', $email); + FreshRSS_Auth::giveAccess(); + invalidateHttpCache(); + } else { + Minz_Log::error($reason); + + $res = array(); + $res['status'] = 'failure'; + $res['reason'] = _t('invalid_login'); + } + + header('Content-Type: application/json; charset=UTF-8'); + $this->view->res = $res; + } + } + + /** + * This action removes all accesses of the current user. + */ + public function logoutAction() { + invalidateHttpCache(); + FreshRSS_Auth::removeAccess(); + Minz_Request::good(_t('disconnected'), + array('c' => 'index', 'a' => 'index')); + } +} diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 3006480f9..5b490e672 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -20,7 +20,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { } elseif ($output !== 'rss') { // "hard" redirection is not required, just ask dispatcher to // forward to the login form without 302 redirection - Minz_Request::forward(array('c' => 'index', 'a' => 'login')); + Minz_Request::forward(array('c' => 'auth', 'a' => 'login')); return; } } @@ -228,92 +228,4 @@ class FreshRSS_index_Controller extends Minz_ActionController { $this->view->logsPaginator->_nbItemsPerPage(50); $this->view->logsPaginator->_currentPage($page); } - - /** - * This action handles the login page. - */ - public function loginAction() { - if (FreshRSS_Auth::hasAccess()) { - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); - } - - invalidateHttpCache(); - - $auth_type = Minz_Configuration::authType(); - switch ($auth_type) { - case 'form': - Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin')); - break; - case 'http_auth': - case 'none': - // It should not happened! - Minz_Error::error(404); - default: - // TODO load plugin instead - Minz_Error::error(404); - } - } - - /** - * - */ - public function formLoginAction() { - if (FreshRSS_Auth::hasAccess()) { - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); - } - - invalidateHttpCache(); - - $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'); - Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime)); - - if (Minz_Request::isPost()) { - $nonce = Minz_Session::param('nonce'); - $username = Minz_Request::param('username', ''); - $challenge = Minz_Request::param('challenge', ''); - try { - $conf = new FreshRSS_Configuration($username); - } catch(Minz_Exception $e) { - // $username is not a valid user, nor the configuration file! - Minz_Log::warning('Login failure: ' . $e->getMessage()); - Minz_Request::bad(_t('invalid_login'), - array('c' => 'index', 'a' => 'login')); - } - - $ok = FreshRSS_FormAuth::checkCredentials( - $username, $conf->passwordHash, $nonce, $challenge - ); - if ($ok) { - // Set session parameter to give access to the user. - Minz_Session::_param('currentUser', $username); - Minz_Session::_param('passwordHash', $conf->passwordHash); - FreshRSS_Auth::giveAccess(); - - // Set cookie parameter if nedded. - if (Minz_Request::param('keep_logged_in', false)) { - FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash); - } else { - FreshRSS_FormAuth::deleteCookie(); - } - - // All is good, go back to the index. - Minz_Request::good(_t('login'), - array('c' => 'index', 'a' => 'index')); - } else { - Minz_Log::warning('Password mismatch for' . - ' user=' . $username . - ', nonce=' . $nonce . - ', c=' . $challenge); - Minz_Request::bad(_t('invalid_login'), - array('c' => 'index', 'a' => 'login')); - } - } - } - - public function logoutAction() { - invalidateHttpCache(); - FreshRSS_Auth::removeAccess(); - Minz_Request::good(_t('disconnected'), - array('c' => 'index', 'a' => 'index')); - } } diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 35a37b887..6b7a813bf 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -64,6 +64,14 @@ class FreshRSS extends Minz_FrontController { Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.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'))); + + if (Minz_Configuration::authType() === 'persona') { + // TODO move it in a plugin + // Needed for login AND logout with Persona. + Minz_View::appendScript('https://login.persona.org/include.js'); + $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/persona.js'); + Minz_View::appendScript(Minz_Url::display('/scripts/persona.js?' . $file_mtime)); + } } private function loadNotifications() { diff --git a/app/Models/Auth.php b/app/Models/Auth.php index 992b444a5..cc23d7974 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -20,7 +20,7 @@ class FreshRSS_Auth { Minz_Session::_param('currentUser', $current_user); } - $access_ok = self::accessControl($current_user); + $access_ok = self::accessControl(); if ($access_ok) { self::giveAccess(); @@ -36,10 +36,9 @@ class FreshRSS_Auth { * Required session parameters are also set in this method (such as * currentUser). * - * @param string $username username of the user to check access. * @return boolean true if user can be connected, false else. */ - public static function accessControl($username) { + public static function accessControl() { if (self::$login_ok) { return true; } @@ -61,6 +60,16 @@ class FreshRSS_Auth { Minz_Session::_param('currentUser', $current_user); } return $login_ok; + case 'persona': + $email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL); + $persona_file = DATA_PATH . '/persona/' . $email . '.txt'; + if (($current_user = @file_get_contents($persona_file)) !== false) { + $current_user = trim($current_user); + Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('mail', $email); + return true; + } + return false; case 'none': return true; default: @@ -87,6 +96,9 @@ class FreshRSS_Auth { case 'http_auth': self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0; break; + case 'persona': + self::$login_ok = strcasecmp(Minz_Session::param('mail'), $conf->mail_login) === 0; + break; case 'none': self::$login_ok = true; break; @@ -131,6 +143,9 @@ class FreshRSS_Auth { Minz_Session::_param('passwordHash'); FreshRSS_FormAuth::deleteCookie(); break; + case 'persona': + Minz_Session::_param('mail'); + break; case 'http_auth': case 'none': // Nothing to do... diff --git a/app/layout/header.phtml b/app/layout/header.phtml index 12c86d61d..deb21edc9 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -2,9 +2,9 @@ if (Minz_Configuration::canLogIn()) { ?>
-