token; $token = Minz_Request::param('token', $current_token); FreshRSS_Context::$user_conf->token = $token; $ok &= FreshRSS_Context::$user_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 != FreshRSS_Context::$system_conf->allow_anonymous || $auth_type != FreshRSS_Context::$system_conf->auth_type || $anon_refresh != FreshRSS_Context::$system_conf->allow_anonymous_refresh || $unsafe_autologin != FreshRSS_Context::$system_conf->unsafe_autologin_enabled || $api_enabled != FreshRSS_Context::$system_conf->api_enabled) { // TODO: test values from form FreshRSS_Context::$system_conf->auth_type = $auth_type; FreshRSS_Context::$system_conf->allow_anonymous = $anon; FreshRSS_Context::$system_conf->allow_anonymous_refresh = $anon_refresh; FreshRSS_Context::$system_conf->unsafe_autologin_enabled = $unsafe_autologin; FreshRSS_Context::$system_conf->api_enabled = $api_enabled; $ok &= FreshRSS_Context::$system_conf->save(); } invalidateHttpCache(); if ($ok) { Minz_Request::good(_t('feedback.conf.updated'), array('c' => 'auth', 'a' => 'index')); } else { Minz_Request::bad(_t('feedback.conf.error'), 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 = FreshRSS_Context::$system_conf->auth_type; 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', ''); $conf = get_user_configuration($username); if (is_null($conf)) { Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false); return; } $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.auth.login.success'), array('c' => 'index', 'a' => 'index')); } else { Minz_Log::warning('Password mismatch for' . ' user=' . $username . ', nonce=' . $nonce . ', c=' . $challenge); Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false); } } elseif (FreshRSS_Context::$system_conf->unsafe_autologin_enabled) { $username = Minz_Request::param('u', ''); $password = Minz_Request::param('p', ''); Minz_Request::_param('p'); if (!$username) { return; } $conf = get_user_configuration($username); if (is_null($conf)) { 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.auth.login.success'), array('c' => 'index', 'a' => 'index')); } else { Minz_Log::warning('Unsafe password mismatch for user ' . $username); Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false); } } } /** * 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); $conf = get_user_configuration($current_user); if (!is_null($conf)) { $login_ok = strcasecmp($email, $conf->mail_login) === 0; } else { $reason = 'Invalid configuration for user ' . '[' . $current_user . ']'; } } } 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::warning($reason); $res = array(); $res['status'] = 'failure'; $res['reason'] = _t('feedback.auth.login.invalid'); } 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.auth.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('admin.auth.title_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 (FreshRSS_Context::$system_conf->auth_type != 'persona') { $this->view->message = array( 'status' => 'bad', 'title' => _t('gen.short.damn'), 'body' => _t('feedback.auth.not_persona') ); $this->view->no_form = true; return; } $conf = get_user_configuration(FreshRSS_Context::$system_conf->default_user); if (is_null($conf)) { return; } // Admin user must have set its master password. if (!$conf->passwordHash) { $this->view->message = array( 'status' => 'bad', 'title' => _t('gen.short.damn'), 'body' => _t('feedback.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) { FreshRSS_Context::$system_conf->auth_type = 'form'; $ok = FreshRSS_Context::$system_conf->save(); if ($ok) { Minz_Request::good(_t('feedback.auth.form.set')); } else { Minz_Request::bad(_t('feedback.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('feedback.auth.login.invalid'), array('c' => 'auth', 'a' => 'reset')); } } } /** * This action gives possibility to a user to create an account. */ public function registerAction() { if (max_registrations_reached()) { Minz_Error::error(403); } Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · '); } }