AuthController is dedicated to auhentication. Persona is back, greater than ever! See https://github.com/marienfressinaud/FreshRSS/issues/655pull/664/head
parent
6009990935
commit
1252b3dd86
11 changed files with 331 additions and 195 deletions
@ -0,0 +1,182 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* This controller handles action about authentication. |
||||
*/ |
||||
class FreshRSS_auth_Controller extends Minz_ActionController { |
||||
/** |
||||
* 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) |
||||
*/ |
||||
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')); |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
<?php if ($this->res === false) { ?> |
||||
<div class="prompt"> |
||||
<h1><?php echo _t('login'); ?></h1>
|
||||
|
||||
<p> |
||||
<a class="signin btn btn-important" href="<?php echo _url('auth', 'login'); ?>">
|
||||
<?php echo _i('login'); ?> <?php echo _t('login_with_persona'); ?> |
||||
</a> |
||||
|
||||
<br /><br /> |
||||
|
||||
<?php echo _i('help'); ?> |
||||
<small> |
||||
<a href="<?php echo _url('auth', 'resetAuth'); ?>"><?php echo _t('login_persona_problem'); ?></a>
|
||||
</small> |
||||
</p> |
||||
|
||||
<p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></p>
|
||||
</div> |
||||
<?php |
||||
} else { |
||||
echo json_encode($this->res); |
||||
} |
||||
?> |
@ -0,0 +1,76 @@ |
||||
"use strict"; |
||||
|
||||
function init_persona() { |
||||
if (!(navigator.id && window.$)) { |
||||
if (window.console) { |
||||
console.log('FreshRSS (Persona) waiting for JS…'); |
||||
} |
||||
window.setTimeout(init_persona, 100); |
||||
return; |
||||
} |
||||
|
||||
$('a.signin').click(function() { |
||||
navigator.id.request(); |
||||
return false; |
||||
}); |
||||
|
||||
$('a.signout').click(function() { |
||||
navigator.id.logout(); |
||||
return false; |
||||
}); |
||||
|
||||
navigator.id.watch({ |
||||
loggedInUser: context['current_user_mail'], |
||||
|
||||
onlogin: function(assertion) { |
||||
// A user has logged in! Here you need to:
|
||||
// 1. Send the assertion to your backend for verification and to create a session.
|
||||
// 2. Update your UI.
|
||||
$.ajax ({ |
||||
type: 'POST', |
||||
url: url['login'], |
||||
data: {assertion: assertion}, |
||||
success: function(res, status, xhr) { |
||||
if (res.status === 'failure') { |
||||
openNotification(res.reason, 'bad'); |
||||
} else if (res.status === 'okay') { |
||||
location.href = url['index']; |
||||
} |
||||
}, |
||||
error: function(res, status, xhr) { |
||||
// alert(res);
|
||||
} |
||||
}); |
||||
}, |
||||
onlogout: function() { |
||||
// A user has logged out! Here you need to:
|
||||
// Tear down the user's session by redirecting the user or making a call to your backend.
|
||||
// Also, make sure loggedInUser will get set to null on the next page load.
|
||||
// (That's a literal JavaScript null. Not false, 0, or undefined. null.)
|
||||
$.ajax ({ |
||||
type: 'POST', |
||||
url: url['logout'], |
||||
success: function(res, status, xhr) { |
||||
location.href = url['index']; |
||||
}, |
||||
error: function(res, status, xhr) { |
||||
// alert(res);
|
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
if (document.readyState && document.readyState !== 'loading') { |
||||
if (window.console) { |
||||
console.log('FreshRSS (Persona) immediate init…'); |
||||
} |
||||
init_persona(); |
||||
} else if (document.addEventListener) { |
||||
document.addEventListener('DOMContentLoaded', function () { |
||||
if (window.console) { |
||||
console.log('FreshRSS (Persona) waiting for DOMContentLoaded…'); |
||||
} |
||||
init_persona(); |
||||
}, false); |
||||
} |
Loading…
Reference in new issue