From 4dd673157b05fea5fe3643f16e22d01bbf005fe9 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 13 Feb 2014 21:45:25 +0100 Subject: Add possibility to anonymous to refresh feeds Obviously, it's optional! (and deactivate by default) Need some more tests? See #351 --- app/Controllers/usersController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'app/Controllers/usersController.php') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index 8314b75fc..bb4f34c5e 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -54,11 +54,16 @@ class FreshRSS_users_Controller extends Minz_ActionController { $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'); if ($anon != Minz_Configuration::allowAnonymous() || - $auth_type != Minz_Configuration::authType()) { + $auth_type != Minz_Configuration::authType() || + $anon_refresh != Minz_Configuration::allowAnonymousRefresh()) { + Minz_Configuration::_authType($auth_type); Minz_Configuration::_allowAnonymous($anon); + Minz_Configuration::_allowAnonymousRefresh($anon_refresh); $ok &= Minz_Configuration::writeFile(); } } -- cgit v1.2.3 From 29b3bbfe284a6e56413a2e89b740ffc4172c6847 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Mar 2014 14:45:58 +0100 Subject: API: Real password system https://github.com/marienfressinaud/FreshRSS/issues/13 Expiring token not implemented yet --- app/Controllers/usersController.php | 12 +++++ app/Models/Configuration.php | 4 ++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/views/configure/users.phtml | 12 ++++- p/api/greader.php | 89 ++++++++++++++++++++++--------------- 6 files changed, 82 insertions(+), 37 deletions(-) (limited to 'app/Controllers/usersController.php') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index bb4f34c5e..b03989cd7 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -32,6 +32,18 @@ class FreshRSS_users_Controller extends Minz_ActionController { } Minz_Session::_param('passwordHash', $this->view->conf->passwordHash); + $passwordPlain = Minz_Request::param('apiPasswordPlain', false); + if ($passwordPlain != '') { + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); + $passwordPlain = ''; + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js + $ok &= ($passwordHash != ''); + $this->view->conf->_apiPasswordHash($passwordHash); + } + if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { $this->view->conf->_mail_login(Minz_Request::param('mail_login', false)); } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 48efe3bf6..827a1d166 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -10,6 +10,7 @@ class FreshRSS_Configuration { 'mail_login' => '', 'token' => '', 'passwordHash' => '', //CRYPT_BLOWFISH + 'apiPasswordHash' => '', //CRYPT_BLOWFISH 'posts_per_page' => 20, 'view_mode' => 'normal', 'default_view' => 'not_read', @@ -165,6 +166,9 @@ class FreshRSS_Configuration { public function _passwordHash ($value) { $this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; } + public function _apiPasswordHash ($value) { + $this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; + } public function _mail_login ($value) { $value = filter_var($value, FILTER_VALIDATE_EMAIL); if ($value) { diff --git a/app/i18n/en.php b/app/i18n/en.php index e67447520..d504ffc11 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -176,6 +176,7 @@ return array ( 'current_user' => 'Current user', 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', 'password_form' => 'Password
(for the Web-form login method)', + 'password_api' => 'Password API
(e.g., for mobile apps)', 'persona_connection_email' => 'Login mail address
(for Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 2bd4fabab..c5581a78b 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -175,6 +175,7 @@ return array ( 'current_user' => 'Utilisateur actuel', 'password_form' => 'Mot de passe
(pour connexion par formulaire)', + 'password_api' => 'Mot de passe API
(ex. : pour applis mobiles)', 'default_user' => 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)', 'persona_connection_email' => 'Adresse courriel de connexion
(pour Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index 0677db881..f5c7dff17 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -20,7 +20,15 @@
- + /> + +
+
+ +
+ +
+ />
@@ -85,7 +93,7 @@ conf->token; ?>
- />
diff --git a/p/api/greader.php b/p/api/greader.php index e99e1c0c8..035a031dd 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -20,9 +20,6 @@ Server-side API compatible with Google Reader API layer 2 * https://github.com/theoldreader/api */ -define('TEMP_PASSWORD', 'temp123'); //Change to another ASCII password -define('TEMP_AUTH', 'XtofqkkOkCULRLH8'); //Change to another random ASCII auth - require('../../constants.php'); require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader @@ -119,14 +116,28 @@ function checkCompatibility() { exit(); } -function authorizationToUser() { +function authorizationToUserConf() { $headerAuth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external if ($headerAuth != '') { $headerAuthX = explode('/', $headerAuth, 2); - if ((count($headerAuthX) === 2) && ($headerAuthX[1] === TEMP_AUTH)) { + if (count($headerAuthX) === 2) { $user = $headerAuthX[0]; if (ctype_alnum($user)) { - return $user; + try { + $conf = new FreshRSS_Configuration($user); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + unauthorized(); + } + if ($headerAuthX[1] === sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash)) { + return $conf; + } else { + logMe('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1] . "\n"); + Minz_Log::record('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1], Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); } } } @@ -135,28 +146,45 @@ function authorizationToUser() { function clientLogin($email, $pass) { //http://web.archive.org/web/20130604091042/http://undoc.in/clientLogin.html logMe('clientLogin(' . $email . ")\n"); - if ($pass !== TEMP_PASSWORD) { - unauthorized(); + if (ctype_alnum($email)) { + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($email); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + Minz_Log::record('Invalid API user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + if ($conf->apiPasswordHash != '' && password_verify($pass, $conf->apiPasswordHash)) { + header('Content-Type: text/plain; charset=UTF-8'); + $auth = $email . '/' . sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash); + echo 'SID=', $auth, "\n", + 'Auth=', $auth, "\n"; + exit(); + } else { + Minz_Log::record('Password API mismatch for user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); } - header('Content-Type: text/plain; charset=UTF-8'); - $auth = $email . '/' . TEMP_AUTH; - echo 'SID=', $auth, "\n", - 'Auth=', $auth, "\n"; - exit(); + die(); } -function token($user) { +function token($conf) { //http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ https://github.com/ericmann/gReader-Library/blob/master/greader.class.php - logMe('token('. $user . ")\n"); - $token = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234'; //Must have 57 characters... + logMe('token('. $conf->user . ")\n"); //TODO: Implement real token that expires + $token = str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z'); //Must have 57 characters echo $token, "\n"; exit(); } -function checkToken($user, $token) { +function checkToken($conf, $token) { //http://code.google.com/p/google-reader-api/wiki/ActionToken logMe('checkToken(' . $token . ")\n"); - if ($token === 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234') { + if ($token === str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z')) { return true; } unauthorized(); @@ -462,32 +490,23 @@ if (!Minz_Configuration::apiEnabled()) { Minz_Session::init('FreshRSS'); -$user = authorizationToUser(); -$conf = null; +$conf = authorizationToUserConf(); +$user = $conf == null ? '' : $conf->user; logMe('User => ' . $user . "\n"); -if ($user != null) { - try { - $conf = new FreshRSS_Configuration($user); - } catch (Exception $e) { - logMe($e->getMessage()); - $user = null; - badRequest(); - } -} - Minz_Session::_param('currentUser', $user); if (count($pathInfos) < 3) { badRequest(); } elseif ($pathInfos[1] === 'accounts') { - if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) + if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) { clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); + } } elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfos[3]) && $pathInfos[3] === '0' && isset($pathInfos[4])) { - if ($user == null) { + if ($user == '') { unauthorized(); } $timestamp = isset($_GET['ck']) ? intval($_GET['ck']) : 0; //ck=[unix timestamp] : Use the current Unix time here, helps Google with caching. @@ -543,7 +562,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo break; case 'edit-tag': //http://blog.martindoms.com/2010/01/20/using-the-google-reader-api-part-3/ $token = isset($_POST['T']) ? trim($_POST['T']) : ''; - checkToken($user, $token); + checkToken($conf, $token); $a = isset($_POST['a']) ? $_POST['a'] : ''; //Add: user/-/state/com.google/read user/-/state/com.google/starred $r = isset($_POST['r']) ? $_POST['r'] : ''; //Remove: user/-/state/com.google/read user/-/state/com.google/starred $e_ids = multiplePosts('i'); //item IDs @@ -551,7 +570,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo break; case 'mark-all-as-read': $token = isset($_POST['T']) ? trim($_POST['T']) : ''; - checkToken($user, $token); + checkToken($conf, $token); $streamId = $_POST['s']; //StreamId $ts = isset($_POST['ts']) ? $_POST['ts'] : '0'; //Older than timestamp in nanoseconds if (!ctype_digit($ts)) { @@ -560,7 +579,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo markAllAsRead($streamId, $ts); break; case 'token': - Token($user); + Token($conf); break; } } elseif ($pathInfos[1] === 'check' && $pathInfos[2] === 'compatibility') { -- cgit v1.2.3 From 670ee57b6288549ac611d53d86d8f5ffee641b0e Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 8 Mar 2014 20:16:10 +0100 Subject: Interface to enable/disable API and unsafe automatic login https://github.com/marienfressinaud/FreshRSS/issues/13 https://github.com/marienfressinaud/FreshRSS/issues/440 --- app/Controllers/usersController.php | 8 +++++++- app/Models/Feed.php | 11 ++++++----- app/i18n/en.php | 2 ++ app/i18n/fr.php | 2 ++ app/views/configure/users.phtml | 21 +++++++++++++++++++++ 5 files changed, 38 insertions(+), 6 deletions(-) (limited to 'app/Controllers/usersController.php') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index b03989cd7..fa967cedc 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -69,13 +69,19 @@ class FreshRSS_users_Controller extends Minz_ActionController { $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()) { + $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(); } } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 73f9c32fb..bce3bd24f 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -254,11 +254,12 @@ class FreshRSS_Feed extends Minz_Model { $elinks = array(); foreach ($item->get_enclosures() as $enclosure) { $elink = $enclosure->get_link(); - if (array_key_exists($elink, $elinks)) continue; - $elinks[$elink] = '1'; - $mime = strtolower($enclosure->get_type()); - if (strpos($mime, 'image/') === 0) { - $content .= '
'; + if (empty($elinks[$elink])) { + $elinks[$elink] = '1'; + $mime = strtolower($enclosure->get_type()); + if (strpos($mime, 'image/') === 0) { + $content .= '
'; + } } } diff --git a/app/i18n/en.php b/app/i18n/en.php index d504ffc11..69247165f 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -180,6 +180,8 @@ return array ( 'persona_connection_email' => 'Login mail address
(for Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', + 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', + 'api_enabled' => 'Allow API access (required for mobile apps)', 'auth_token' => 'Authentication token', 'explain_token' => 'Allows to access RSS output of the default user without authentication.
%s?output=rss&token=%s', 'login_configuration' => 'Login', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index c5581a78b..e364ed130 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -180,6 +180,8 @@ return array ( 'persona_connection_email' => 'Adresse courriel de connexion
(pour Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', 'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux', + 'unsafe_autologin' => 'Autoriser les connexion automatiques non-sûres au format : ', + 'api_enabled' => 'Autoriser l’accès par API (nécessaire pour les applis mobiles)', 'auth_token' => 'Jeton d’identification', 'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.
%s?output=rss&token=%s', 'login_configuration' => 'Identification', diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index 3f3f19418..1a02b0e90 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -90,6 +90,17 @@ +
+
+ +
+
+
@@ -102,6 +113,16 @@
+
+
+ +
+
+
-- cgit v1.2.3