summaryrefslogtreecommitdiff
path: root/app/Controllers/authController.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/Controllers/authController.php')
-rw-r--r--app/Controllers/authController.php349
1 files changed, 349 insertions, 0 deletions
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
new file mode 100644
index 000000000..44496cd3e
--- /dev/null
+++ b/app/Controllers/authController.php
@@ -0,0 +1,349 @@
+<?php
+
+/**
+ * This controller handles action about authentication.
+ */
+class FreshRSS_auth_Controller extends Minz_ActionController {
+ /**
+ * This action handles authentication management page.
+ *
+ * Parameters are:
+ * - token (default: current token)
+ * - anon_access (default: false)
+ * - anon_refresh (default: false)
+ * - auth_type (default: none)
+ * - unsafe_autologin (default: false)
+ * - api_enabled (default: false)
+ *
+ * @todo move unsafe_autologin in an extension.
+ */
+ public function indexAction() {
+ if (!FreshRSS_Auth::hasAccess('admin')) {
+ Minz_Error::error(403);
+ }
+
+ Minz_View::prependTitle(_t('gen.title.authentication') . ' · ');
+
+ if (Minz_Request::isPost()) {
+ $ok = true;
+
+ $current_token = FreshRSS_Context::$conf->token;
+ $token = Minz_Request::param('token', $current_token);
+ FreshRSS_Context::$conf->_token($token);
+ $ok &= FreshRSS_Context::$conf->save();
+
+ $anon = Minz_Request::param('anon_access', false);
+ $anon = ((bool)$anon) && ($anon !== 'no');
+ $anon_refresh = Minz_Request::param('anon_refresh', false);
+ $anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no');
+ $auth_type = Minz_Request::param('auth_type', 'none');
+ $unsafe_autologin = Minz_Request::param('unsafe_autologin', false);
+ $api_enabled = Minz_Request::param('api_enabled', false);
+ if ($anon != Minz_Configuration::allowAnonymous() ||
+ $auth_type != Minz_Configuration::authType() ||
+ $anon_refresh != Minz_Configuration::allowAnonymousRefresh() ||
+ $unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() ||
+ $api_enabled != Minz_Configuration::apiEnabled()) {
+
+ Minz_Configuration::_authType($auth_type);
+ Minz_Configuration::_allowAnonymous($anon);
+ Minz_Configuration::_allowAnonymousRefresh($anon_refresh);
+ Minz_Configuration::_enableAutologin($unsafe_autologin);
+ Minz_Configuration::_enableApi($api_enabled);
+ $ok &= Minz_Configuration::writeFile();
+ }
+
+ invalidateHttpCache();
+
+ if ($ok) {
+ Minz_Request::good(_t('configuration_updated'),
+ array('c' => 'auth', 'a' => 'index'));
+ } else {
+ Minz_Request::bad(_t('error_occurred'),
+ array('c' => 'auth', 'a' => 'index'));
+ }
+ }
+ }
+
+ /**
+ * This action handles the login page.
+ *
+ * It forwards to the correct login page (form or Persona) or main page if
+ * the user is already connected.
+ */
+ public function loginAction() {
+ if (FreshRSS_Auth::hasAccess()) {
+ Minz_Request::forward(array('c' => '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)
+ *
+ * @todo move unsafe autologin in an extension.
+ */
+ 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('feedback.login.success'),
+ 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'));
+ }
+ } elseif (Minz_Configuration::unsafeAutologinEnabled()) {
+ $username = Minz_Request::param('u', '');
+ $password = Minz_Request::param('p', '');
+ Minz_Request::_param('p');
+
+ if (!$username) {
+ return;
+ }
+
+ 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());
+ return;
+ }
+
+ if (!function_exists('password_verify')) {
+ include_once(LIB_PATH . '/password_compat.php');
+ }
+
+ $s = $conf->passwordHash;
+ $ok = password_verify($password, $s);
+ unset($password);
+ if ($ok) {
+ Minz_Session::_param('currentUser', $username);
+ Minz_Session::_param('passwordHash', $s);
+ FreshRSS_Auth::giveAccess();
+
+ Minz_Request::good(_t('feedback.login.success'),
+ array('c' => 'index', 'a' => 'index'));
+ } else {
+ Minz_Log::warning('Unsafe password mismatch for user ' . $username);
+ 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('feedback.logout.success'),
+ array('c' => 'index', 'a' => 'index'));
+ }
+
+ /**
+ * This action resets the authentication system.
+ *
+ * After reseting, form auth is set by default.
+ */
+ public function resetAction() {
+ Minz_View::prependTitle(_t('auth_reset') . ' · ');
+
+ Minz_View::appendScript(Minz_Url::display(
+ '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')
+ ));
+
+ $this->view->no_form = false;
+ // Enable changement of auth only if Persona!
+ if (Minz_Configuration::authType() != 'persona') {
+ $this->view->message = array(
+ 'status' => 'bad',
+ 'title' => _t('damn'),
+ 'body' => _t('auth_not_persona')
+ );
+ $this->view->no_form = true;
+ return;
+ }
+
+ $conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser());
+ // Admin user must have set its master password.
+ if (!$conf->passwordHash) {
+ $this->view->message = array(
+ 'status' => 'bad',
+ 'title' => _t('damn'),
+ 'body' => _t('auth_no_password_set')
+ );
+ $this->view->no_form = true;
+ return;
+ }
+
+ invalidateHttpCache();
+
+ if (Minz_Request::isPost()) {
+ $nonce = Minz_Session::param('nonce');
+ $username = Minz_Request::param('username', '');
+ $challenge = Minz_Request::param('challenge', '');
+
+ $ok = FreshRSS_FormAuth::checkCredentials(
+ $username, $conf->passwordHash, $nonce, $challenge
+ );
+
+ if ($ok) {
+ Minz_Configuration::_authType('form');
+ $ok = Minz_Configuration::writeFile();
+
+ if ($ok) {
+ Minz_Request::good(_t('auth_form_set'));
+ } else {
+ Minz_Request::bad(_t('auth_form_not_set'),
+ array('c' => 'auth', 'a' => 'reset'));
+ }
+ } else {
+ Minz_Log::warning('Password mismatch for' .
+ ' user=' . $username .
+ ', nonce=' . $nonce .
+ ', c=' . $challenge);
+ Minz_Request::bad(_t('invalid_login'),
+ array('c' => 'auth', 'a' => 'reset'));
+ }
+ }
+ }
+}