Merge pull request #1086 from virtualtam/refactor/login

Refactor user login and session management
This commit is contained in:
VirtualTam 2018-06-03 18:26:32 +02:00 committed by GitHub
commit d9cd27322a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1116 additions and 563 deletions

View file

@ -1,7 +1,7 @@
<?php <?php
/** /**
* GET an HTTP URL to retrieve its content * GET an HTTP URL to retrieve its content
* Uses the cURL library or a fallback method * Uses the cURL library or a fallback method
* *
* @param string $url URL to get (http://...) * @param string $url URL to get (http://...)
* @param int $timeout network timeout (in seconds) * @param int $timeout network timeout (in seconds)
@ -415,6 +415,37 @@ function getIpAddressFromProxy($server, $trustedIps)
return array_pop($ips); return array_pop($ips);
} }
/**
* Return an identifier based on the advertised client IP address(es)
*
* This aims at preventing session hijacking from users behind the same proxy
* by relying on HTTP headers.
*
* See:
* - https://secure.php.net/manual/en/reserved.variables.server.php
* - https://stackoverflow.com/questions/3003145/how-to-get-the-client-ip-address-in-php
* - https://stackoverflow.com/questions/12233406/preventing-session-hijacking
* - https://stackoverflow.com/questions/21354859/trusting-x-forwarded-for-to-identify-a-visitor
*
* @param array $server The $_SERVER array
*
* @return string An identifier based on client IP address information
*/
function client_ip_id($server)
{
$ip = $server['REMOTE_ADDR'];
if (isset($server['HTTP_X_FORWARDED_FOR'])) {
$ip = $ip . '_' . $server['HTTP_X_FORWARDED_FOR'];
}
if (isset($server['HTTP_CLIENT_IP'])) {
$ip = $ip . '_' . $server['HTTP_CLIENT_IP'];
}
return $ip;
}
/** /**
* Returns true if Shaarli's currently browsed in HTTPS. * Returns true if Shaarli's currently browsed in HTTPS.
* Supports reverse proxies (if the headers are correctly set). * Supports reverse proxies (if the headers are correctly set).

View file

@ -1,134 +0,0 @@
<?php
namespace Shaarli;
/**
* User login management
*/
class LoginManager
{
protected $globals = [];
protected $configManager = null;
protected $banFile = '';
/**
* Constructor
*
* @param array $globals The $GLOBALS array (reference)
* @param ConfigManager $configManager Configuration Manager instance.
*/
public function __construct(& $globals, $configManager)
{
$this->globals = &$globals;
$this->configManager = $configManager;
$this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
$this->readBanFile();
}
/**
* Read a file containing banned IPs
*/
protected function readBanFile()
{
if (! file_exists($this->banFile)) {
return;
}
include $this->banFile;
}
/**
* Write the banned IPs to a file
*/
protected function writeBanFile()
{
if (! array_key_exists('IPBANS', $this->globals)) {
return;
}
file_put_contents(
$this->banFile,
"<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
);
}
/**
* Handle a failed login and ban the IP after too many failed attempts
*
* @param array $server The $_SERVER array
*/
public function handleFailedLogin($server)
{
$ip = $server['REMOTE_ADDR'];
$trusted = $this->configManager->get('security.trusted_proxies', []);
if (in_array($ip, $trusted)) {
$ip = getIpAddressFromProxy($server, $trusted);
if (! $ip) {
// the IP is behind a trusted forward proxy, but is not forwarded
// in the HTTP headers, so we do nothing
return;
}
}
// increment the fail count for this IP
if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
$this->globals['IPBANS']['FAILURES'][$ip]++;
} else {
$this->globals['IPBANS']['FAILURES'][$ip] = 1;
}
if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
$this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
logm(
$this->configManager->get('resource.log'),
$server['REMOTE_ADDR'],
'IP address banned from login'
);
}
$this->writeBanFile();
}
/**
* Handle a successful login
*
* @param array $server The $_SERVER array
*/
public function handleSuccessfulLogin($server)
{
$ip = $server['REMOTE_ADDR'];
// FIXME unban when behind a trusted proxy?
unset($this->globals['IPBANS']['FAILURES'][$ip]);
unset($this->globals['IPBANS']['BANS'][$ip]);
$this->writeBanFile();
}
/**
* Check if the user can login from this IP
*
* @param array $server The $_SERVER array
*
* @return bool true if the user is allowed to login
*/
public function canLogin($server)
{
$ip = $server['REMOTE_ADDR'];
if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
// the user is not banned
return true;
}
if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
// the user is still banned
return false;
}
// the ban has expired, the user can attempt to log in again
logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
unset($this->globals['IPBANS']['FAILURES'][$ip]);
unset($this->globals['IPBANS']['BANS'][$ip]);
$this->writeBanFile();
return true;
}
}

View file

@ -25,6 +25,9 @@ class PageBuilder
* @var LinkDB $linkDB instance. * @var LinkDB $linkDB instance.
*/ */
protected $linkDB; protected $linkDB;
/** @var bool $isLoggedIn Whether the user is logged in **/
protected $isLoggedIn = false;
/** /**
* PageBuilder constructor. * PageBuilder constructor.
@ -34,12 +37,13 @@ class PageBuilder
* @param LinkDB $linkDB instance. * @param LinkDB $linkDB instance.
* @param string $token Session token * @param string $token Session token
*/ */
public function __construct(&$conf, $linkDB = null, $token = null) public function __construct(&$conf, $linkDB = null, $token = null, $isLoggedIn = false)
{ {
$this->tpl = false; $this->tpl = false;
$this->conf = $conf; $this->conf = $conf;
$this->linkDB = $linkDB; $this->linkDB = $linkDB;
$this->token = $token; $this->token = $token;
$this->isLoggedIn = $isLoggedIn;
} }
/** /**
@ -55,7 +59,7 @@ private function initialize()
$this->conf->get('resource.update_check'), $this->conf->get('resource.update_check'),
$this->conf->get('updates.check_updates_interval'), $this->conf->get('updates.check_updates_interval'),
$this->conf->get('updates.check_updates'), $this->conf->get('updates.check_updates'),
isLoggedIn(), $this->isLoggedIn,
$this->conf->get('updates.check_updates_branch') $this->conf->get('updates.check_updates_branch')
); );
$this->tpl->assign('newVersion', escape($version)); $this->tpl->assign('newVersion', escape($version));
@ -67,6 +71,7 @@ private function initialize()
$this->tpl->assign('versionError', escape($exc->getMessage())); $this->tpl->assign('versionError', escape($exc->getMessage()));
} }
$this->tpl->assign('is_logged_in', $this->isLoggedIn);
$this->tpl->assign('feedurl', escape(index_url($_SERVER))); $this->tpl->assign('feedurl', escape(index_url($_SERVER)));
$searchcrits = ''; // Search criteria $searchcrits = ''; // Search criteria
if (!empty($_GET['searchtags'])) { if (!empty($_GET['searchtags'])) {

View file

@ -1,83 +0,0 @@
<?php
namespace Shaarli;
/**
* Manages the server-side session
*/
class SessionManager
{
protected $session = [];
/**
* Constructor
*
* @param array $session The $_SESSION array (reference)
* @param ConfigManager $conf ConfigManager instance
*/
public function __construct(& $session, $conf)
{
$this->session = &$session;
$this->conf = $conf;
}
/**
* Generates a session token
*
* @return string token
*/
public function generateToken()
{
$token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
$this->session['tokens'][$token] = 1;
return $token;
}
/**
* Checks the validity of a session token, and destroys it afterwards
*
* @param string $token The token to check
*
* @return bool true if the token is valid, else false
*/
public function checkToken($token)
{
if (! isset($this->session['tokens'][$token])) {
// the token is wrong, or has already been used
return false;
}
// destroy the token to prevent future use
unset($this->session['tokens'][$token]);
return true;
}
/**
* Validate session ID to prevent Full Path Disclosure.
*
* See #298.
* The session ID's format depends on the hash algorithm set in PHP settings
*
* @param string $sessionId Session ID
*
* @return true if valid, false otherwise.
*
* @see http://php.net/manual/en/function.hash-algos.php
* @see http://php.net/manual/en/session.configuration.php
*/
public static function checkId($sessionId)
{
if (empty($sessionId)) {
return false;
}
if (!$sessionId) {
return false;
}
if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,265 @@
<?php
namespace Shaarli\Security;
use Shaarli\Config\ConfigManager;
/**
* User login management
*/
class LoginManager
{
/** @var string Name of the cookie set after logging in **/
public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn';
/** @var array A reference to the $_GLOBALS array */
protected $globals = [];
/** @var ConfigManager Configuration Manager instance **/
protected $configManager = null;
/** @var SessionManager Session Manager instance **/
protected $sessionManager = null;
/** @var string Path to the file containing IP bans */
protected $banFile = '';
/** @var bool Whether the user is logged in **/
protected $isLoggedIn = false;
/** @var bool Whether the Shaarli instance is open to public edition **/
protected $openShaarli = false;
/** @var string User sign-in token depending on remote IP and credentials */
protected $staySignedInToken = '';
/**
* Constructor
*
* @param array $globals The $GLOBALS array (reference)
* @param ConfigManager $configManager Configuration Manager instance
* @param SessionManager $sessionManager SessionManager instance
*/
public function __construct(& $globals, $configManager, $sessionManager)
{
$this->globals = &$globals;
$this->configManager = $configManager;
$this->sessionManager = $sessionManager;
$this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
$this->readBanFile();
if ($this->configManager->get('security.open_shaarli') === true) {
$this->openShaarli = true;
}
}
/**
* Generate a token depending on deployment salt, user password and client IP
*
* @param string $clientIpAddress The remote client IP address
*/
public function generateStaySignedInToken($clientIpAddress)
{
$this->staySignedInToken = sha1(
$this->configManager->get('credentials.hash')
. $clientIpAddress
. $this->configManager->get('credentials.salt')
);
}
/**
* Return the user's client stay-signed-in token
*
* @return string User's client stay-signed-in token
*/
public function getStaySignedInToken()
{
return $this->staySignedInToken;
}
/**
* Check user session state and validity (expiration)
*
* @param array $cookie The $_COOKIE array
* @param string $clientIpId Client IP address identifier
*/
public function checkLoginState($cookie, $clientIpId)
{
if (! $this->configManager->exists('credentials.login')) {
// Shaarli is not configured yet
$this->isLoggedIn = false;
return;
}
if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE])
&& $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
) {
// The user client has a valid stay-signed-in cookie
// Session information is updated with the current client information
$this->sessionManager->storeLoginInfo($clientIpId);
} elseif ($this->sessionManager->hasSessionExpired()
|| $this->sessionManager->hasClientIpChanged($clientIpId)
) {
$this->sessionManager->logout();
$this->isLoggedIn = false;
return;
}
$this->isLoggedIn = true;
$this->sessionManager->extendSession();
}
/**
* Return whether the user is currently logged in
*
* @return true when the user is logged in, false otherwise
*/
public function isLoggedIn()
{
if ($this->openShaarli) {
return true;
}
return $this->isLoggedIn;
}
/**
* Check user credentials are valid
*
* @param string $remoteIp Remote client IP address
* @param string $clientIpId Client IP address identifier
* @param string $login Username
* @param string $password Password
*
* @return bool true if the provided credentials are valid, false otherwise
*/
public function checkCredentials($remoteIp, $clientIpId, $login, $password)
{
$hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
if ($login != $this->configManager->get('credentials.login')
|| $hash != $this->configManager->get('credentials.hash')
) {
logm(
$this->configManager->get('resource.log'),
$remoteIp,
'Login failed for user ' . $login
);
return false;
}
$this->sessionManager->storeLoginInfo($clientIpId);
logm(
$this->configManager->get('resource.log'),
$remoteIp,
'Login successful'
);
return true;
}
/**
* Read a file containing banned IPs
*/
protected function readBanFile()
{
if (! file_exists($this->banFile)) {
return;
}
include $this->banFile;
}
/**
* Write the banned IPs to a file
*/
protected function writeBanFile()
{
if (! array_key_exists('IPBANS', $this->globals)) {
return;
}
file_put_contents(
$this->banFile,
"<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
);
}
/**
* Handle a failed login and ban the IP after too many failed attempts
*
* @param array $server The $_SERVER array
*/
public function handleFailedLogin($server)
{
$ip = $server['REMOTE_ADDR'];
$trusted = $this->configManager->get('security.trusted_proxies', []);
if (in_array($ip, $trusted)) {
$ip = getIpAddressFromProxy($server, $trusted);
if (! $ip) {
// the IP is behind a trusted forward proxy, but is not forwarded
// in the HTTP headers, so we do nothing
return;
}
}
// increment the fail count for this IP
if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
$this->globals['IPBANS']['FAILURES'][$ip]++;
} else {
$this->globals['IPBANS']['FAILURES'][$ip] = 1;
}
if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
$this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
logm(
$this->configManager->get('resource.log'),
$server['REMOTE_ADDR'],
'IP address banned from login'
);
}
$this->writeBanFile();
}
/**
* Handle a successful login
*
* @param array $server The $_SERVER array
*/
public function handleSuccessfulLogin($server)
{
$ip = $server['REMOTE_ADDR'];
// FIXME unban when behind a trusted proxy?
unset($this->globals['IPBANS']['FAILURES'][$ip]);
unset($this->globals['IPBANS']['BANS'][$ip]);
$this->writeBanFile();
}
/**
* Check if the user can login from this IP
*
* @param array $server The $_SERVER array
*
* @return bool true if the user is allowed to login
*/
public function canLogin($server)
{
$ip = $server['REMOTE_ADDR'];
if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
// the user is not banned
return true;
}
if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
// the user is still banned
return false;
}
// the ban has expired, the user can attempt to log in again
logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
unset($this->globals['IPBANS']['FAILURES'][$ip]);
unset($this->globals['IPBANS']['BANS'][$ip]);
$this->writeBanFile();
return true;
}
}

View file

@ -0,0 +1,199 @@
<?php
namespace Shaarli\Security;
use Shaarli\Config\ConfigManager;
/**
* Manages the server-side session
*/
class SessionManager
{
/** @var int Session expiration timeout, in seconds */
public static $SHORT_TIMEOUT = 3600; // 1 hour
/** @var int Session expiration timeout, in seconds */
public static $LONG_TIMEOUT = 31536000; // 1 year
/** @var array Local reference to the global $_SESSION array */
protected $session = [];
/** @var ConfigManager Configuration Manager instance **/
protected $conf = null;
/** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */
protected $staySignedIn = false;
/**
* Constructor
*
* @param array $session The $_SESSION array (reference)
* @param ConfigManager $conf ConfigManager instance
*/
public function __construct(& $session, $conf)
{
$this->session = &$session;
$this->conf = $conf;
}
/**
* Define whether the user should stay signed in across browser sessions
*
* @param bool $staySignedIn Keep the user signed in
*/
public function setStaySignedIn($staySignedIn)
{
$this->staySignedIn = $staySignedIn;
}
/**
* Generates a session token
*
* @return string token
*/
public function generateToken()
{
$token = sha1(uniqid('', true) .'_'. mt_rand() . $this->conf->get('credentials.salt'));
$this->session['tokens'][$token] = 1;
return $token;
}
/**
* Checks the validity of a session token, and destroys it afterwards
*
* @param string $token The token to check
*
* @return bool true if the token is valid, else false
*/
public function checkToken($token)
{
if (! isset($this->session['tokens'][$token])) {
// the token is wrong, or has already been used
return false;
}
// destroy the token to prevent future use
unset($this->session['tokens'][$token]);
return true;
}
/**
* Validate session ID to prevent Full Path Disclosure.
*
* See #298.
* The session ID's format depends on the hash algorithm set in PHP settings
*
* @param string $sessionId Session ID
*
* @return true if valid, false otherwise.
*
* @see http://php.net/manual/en/function.hash-algos.php
* @see http://php.net/manual/en/session.configuration.php
*/
public static function checkId($sessionId)
{
if (empty($sessionId)) {
return false;
}
if (!$sessionId) {
return false;
}
if (!preg_match('/^[a-zA-Z0-9,-]{2,128}$/', $sessionId)) {
return false;
}
return true;
}
/**
* Store user login information after a successful login
*
* @param string $clientIpId Client IP address identifier
*/
public function storeLoginInfo($clientIpId)
{
$this->session['ip'] = $clientIpId;
$this->session['username'] = $this->conf->get('credentials.login');
$this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
}
/**
* Extend session validity
*/
public function extendSession()
{
if ($this->staySignedIn) {
return $this->extendTimeValidityBy(self::$LONG_TIMEOUT);
}
return $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
}
/**
* Extend expiration time
*
* @param int $duration Expiration time extension (seconds)
*
* @return int New session expiration time
*/
protected function extendTimeValidityBy($duration)
{
$expirationTime = time() + $duration;
$this->session['expires_on'] = $expirationTime;
return $expirationTime;
}
/**
* Logout a user by unsetting all login information
*
* See:
* - https://secure.php.net/manual/en/function.setcookie.php
*/
public function logout()
{
if (isset($this->session)) {
unset($this->session['ip']);
unset($this->session['expires_on']);
unset($this->session['username']);
unset($this->session['visibility']);
unset($this->session['untaggedonly']);
}
}
/**
* Check whether the session has expired
*
* @param string $clientIpId Client IP address identifier
*
* @return bool true if the session has expired, false otherwise
*/
public function hasSessionExpired()
{
if (empty($this->session['expires_on'])) {
return true;
}
if (time() >= $this->session['expires_on']) {
return true;
}
return false;
}
/**
* Check whether the client IP address has changed
*
* @param string $clientIpId Client IP address identifier
*
* @return bool true if the IP has changed, false if it has not, or
* if session protection has been disabled
*/
public function hasClientIpChanged($clientIpId)
{
if ($this->conf->get('security.session_protection_disabled') === true) {
return false;
}
if (isset($this->session['ip']) && $this->session['ip'] === $clientIpId) {
return false;
}
return true;
}
}

View file

@ -36,7 +36,8 @@
"Shaarli\\Api\\Controllers\\": "application/api/controllers", "Shaarli\\Api\\Controllers\\": "application/api/controllers",
"Shaarli\\Api\\Exceptions\\": "application/api/exceptions", "Shaarli\\Api\\Exceptions\\": "application/api/exceptions",
"Shaarli\\Config\\": "application/config/", "Shaarli\\Config\\": "application/config/",
"Shaarli\\Config\\Exception\\": "application/config/exception" "Shaarli\\Config\\Exception\\": "application/config/exception",
"Shaarli\\Security\\": "application/security"
} }
} }
} }

240
index.php
View file

@ -78,8 +78,8 @@
use \Shaarli\Languages; use \Shaarli\Languages;
use \Shaarli\ThemeUtils; use \Shaarli\ThemeUtils;
use \Shaarli\Config\ConfigManager; use \Shaarli\Config\ConfigManager;
use \Shaarli\LoginManager; use \Shaarli\Security\LoginManager;
use \Shaarli\SessionManager; use \Shaarli\Security\SessionManager;
// Ensure the PHP version is supported // Ensure the PHP version is supported
try { try {
@ -101,8 +101,6 @@
// Set default cookie expiration and path. // Set default cookie expiration and path.
session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']); session_set_cookie_params($cookie['lifetime'], $cookiedir, $_SERVER['SERVER_NAME']);
// Set session parameters on server side. // Set session parameters on server side.
// If the user does not access any page within this time, his/her session is considered expired.
define('INACTIVITY_TIMEOUT', 3600); // in seconds.
// Use cookies to store session. // Use cookies to store session.
ini_set('session.use_cookies', 1); ini_set('session.use_cookies', 1);
// Force cookies for session (phpsessionID forbidden in URL). // Force cookies for session (phpsessionID forbidden in URL).
@ -123,8 +121,10 @@
} }
$conf = new ConfigManager(); $conf = new ConfigManager();
$loginManager = new LoginManager($GLOBALS, $conf);
$sessionManager = new SessionManager($_SESSION, $conf); $sessionManager = new SessionManager($_SESSION, $conf);
$loginManager = new LoginManager($GLOBALS, $conf, $sessionManager);
$loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
$clientIpId = client_ip_id($_SERVER);
// LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead. // LC_MESSAGES isn't defined without php-intl, in this case use LC_COLLATE locale instead.
if (! defined('LC_MESSAGES')) { if (! defined('LC_MESSAGES')) {
@ -177,157 +177,61 @@
install($conf, $sessionManager); install($conf, $sessionManager);
} }
// a token depending of deployment salt, user password, and the current ip $loginManager->checkLoginState($_COOKIE, $clientIpId);
define('STAY_SIGNED_IN_TOKEN', sha1($conf->get('credentials.hash') . $_SERVER['REMOTE_ADDR'] . $conf->get('credentials.salt')));
/** /**
* Checking session state (i.e. is the user still logged in) * Adapter function to ensure compatibility with third-party templates
* *
* @param ConfigManager $conf The configuration manager. * @see https://github.com/shaarli/Shaarli/pull/1086
* *
* @return bool: true if the user is logged in, false otherwise. * @return bool true when the user is logged in, false otherwise
*/ */
function setup_login_state($conf)
{
if ($conf->get('security.open_shaarli')) {
return true;
}
$userIsLoggedIn = false; // By default, we do not consider the user as logged in;
$loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
if (! $conf->exists('credentials.login')) {
$userIsLoggedIn = false; // Shaarli is not configured yet.
$loginFailure = true;
}
if (isset($_COOKIE['shaarli_staySignedIn']) &&
$_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
!$loginFailure)
{
fillSessionInfo($conf);
$userIsLoggedIn = true;
}
// If session does not exist on server side, or IP address has changed, or session has expired, logout.
if (empty($_SESSION['uid'])
|| ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs())
|| time() >= $_SESSION['expires_on'])
{
logout();
$userIsLoggedIn = false;
$loginFailure = true;
}
if (!empty($_SESSION['longlastingsession'])) {
$_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
}
else {
$_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
}
if (!$loginFailure) {
$userIsLoggedIn = true;
}
return $userIsLoggedIn;
}
$userIsLoggedIn = setup_login_state($conf);
// ------------------------------------------------------------------------------------------
// Session management
// Returns the IP address of the client (Used to prevent session cookie hijacking.)
function allIPs()
{
$ip = $_SERVER['REMOTE_ADDR'];
// Then we use more HTTP headers to prevent session hijacking from users behind the same proxy.
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; }
if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; }
return $ip;
}
/**
* Load user session.
*
* @param ConfigManager $conf Configuration Manager instance.
*/
function fillSessionInfo($conf)
{
$_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // Generate unique random number (different than phpsessionid)
$_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
$_SESSION['username']= $conf->get('credentials.login');
$_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
}
/**
* Check that user/password is correct.
*
* @param string $login Username
* @param string $password User password
* @param ConfigManager $conf Configuration Manager instance.
*
* @return bool: authentication successful or not.
*/
function check_auth($login, $password, $conf)
{
$hash = sha1($password . $login . $conf->get('credentials.salt'));
if ($login == $conf->get('credentials.login') && $hash == $conf->get('credentials.hash'))
{ // Login/password is correct.
fillSessionInfo($conf);
logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login successful');
return true;
}
logm($conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], 'Login failed for user '.$login);
return false;
}
// Returns true if the user is logged in.
function isLoggedIn() function isLoggedIn()
{ {
global $userIsLoggedIn; global $loginManager;
return $userIsLoggedIn; return $loginManager->isLoggedIn();
} }
// Force logout.
function logout() {
if (isset($_SESSION)) {
unset($_SESSION['uid']);
unset($_SESSION['ip']);
unset($_SESSION['username']);
unset($_SESSION['visibility']);
unset($_SESSION['untaggedonly']);
}
setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
}
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------
// Process login form: Check if login/password is correct. // Process login form: Check if login/password is correct.
if (isset($_POST['login'])) if (isset($_POST['login'])) {
{
if (! $loginManager->canLogin($_SERVER)) { if (! $loginManager->canLogin($_SERVER)) {
die(t('I said: NO. You are banned for the moment. Go away.')); die(t('I said: NO. You are banned for the moment. Go away.'));
} }
if (isset($_POST['password']) if (isset($_POST['password'])
&& $sessionManager->checkToken($_POST['token']) && $sessionManager->checkToken($_POST['token'])
&& (check_auth($_POST['login'], $_POST['password'], $conf)) && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password'])
) { ) {
// Login/password is OK.
$loginManager->handleSuccessfulLogin($_SERVER); $loginManager->handleSuccessfulLogin($_SERVER);
// If user wants to keep the session cookie even after the browser closes: $cookiedir = '';
if (!empty($_POST['longlastingsession'])) { if (dirname($_SERVER['SCRIPT_NAME']) != '/') {
$_SESSION['longlastingsession'] = 31536000; // (31536000 seconds = 1 year)
$expiration = time() + $_SESSION['longlastingsession']; // calculate relative cookie expiration (1 year from now)
setcookie('shaarli_staySignedIn', STAY_SIGNED_IN_TOKEN, $expiration, WEB_PATH);
$_SESSION['expires_on'] = $expiration; // Set session expiration on server-side.
$cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
// Note: Never forget the trailing slash on the cookie path! // Note: Never forget the trailing slash on the cookie path!
session_regenerate_id(true); // Send cookie with new expiration date to browser. $cookiedir = dirname($_SERVER["SCRIPT_NAME"]) . '/';
} }
else // Standard session expiration (=when browser closes)
{ if (!empty($_POST['longlastingsession'])) {
$cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/'; // Keep the session cookie even after the browser closes
session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes" $sessionManager->setStaySignedIn(true);
session_regenerate_id(true); $expirationTime = $sessionManager->extendSession();
setcookie(
$loginManager::$STAY_SIGNED_IN_COOKIE,
$loginManager->getStaySignedInToken(),
$expirationTime,
WEB_PATH
);
} else {
// Standard session expiration (=when browser closes)
$expirationTime = 0;
} }
// Send cookie with the new expiration date to the browser
session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']);
session_regenerate_id(true);
// Optional redirect after login: // Optional redirect after login:
if (isset($_GET['post'])) { if (isset($_GET['post'])) {
$uri = '?post='. urlencode($_GET['post']); $uri = '?post='. urlencode($_GET['post']);
@ -380,15 +284,16 @@ function logout() {
* Gives the last 7 days (which have links). * Gives the last 7 days (which have links).
* This RSS feed cannot be filtered. * This RSS feed cannot be filtered.
* *
* @param ConfigManager $conf Configuration Manager instance. * @param ConfigManager $conf Configuration Manager instance
* @param LoginManager $loginManager LoginManager instance
*/ */
function showDailyRSS($conf) { function showDailyRSS($conf, $loginManager) {
// Cache system // Cache system
$query = $_SERVER['QUERY_STRING']; $query = $_SERVER['QUERY_STRING'];
$cache = new CachedPage( $cache = new CachedPage(
$conf->get('config.PAGE_CACHE'), $conf->get('config.PAGE_CACHE'),
page_url($_SERVER), page_url($_SERVER),
startsWith($query,'do=dailyrss') && !isLoggedIn() startsWith($query,'do=dailyrss') && !$loginManager->isLoggedIn()
); );
$cached = $cache->cachedVersion(); $cached = $cache->cachedVersion();
if (!empty($cached)) { if (!empty($cached)) {
@ -400,7 +305,7 @@ function showDailyRSS($conf) {
// Read links from database (and filter private links if used it not logged in). // Read links from database (and filter private links if used it not logged in).
$LINKSDB = new LinkDB( $LINKSDB = new LinkDB(
$conf->get('resource.datastore'), $conf->get('resource.datastore'),
isLoggedIn(), $loginManager->isLoggedIn(),
$conf->get('privacy.hide_public_links'), $conf->get('privacy.hide_public_links'),
$conf->get('redirector.url'), $conf->get('redirector.url'),
$conf->get('redirector.encode_url') $conf->get('redirector.encode_url')
@ -482,9 +387,10 @@ function showDailyRSS($conf) {
* @param PageBuilder $pageBuilder Template engine wrapper. * @param PageBuilder $pageBuilder Template engine wrapper.
* @param LinkDB $LINKSDB LinkDB instance. * @param LinkDB $LINKSDB LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance. * @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instane. * @param PluginManager $pluginManager Plugin Manager instance.
* @param LoginManager $loginManager Login Manager instance
*/ */
function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager) function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
{ {
$day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD. $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
if (isset($_GET['day'])) { if (isset($_GET['day'])) {
@ -542,7 +448,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
/* Hook is called before column construction so that plugins don't have /* Hook is called before column construction so that plugins don't have
to deal with columns. */ to deal with columns. */
$pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn())); $pluginManager->executeHooks('render_daily', $data, array('loggedin' => $loginManager->isLoggedIn()));
/* We need to spread the articles on 3 columns. /* We need to spread the articles on 3 columns.
I did not want to use a JavaScript lib like http://masonry.desandro.com/ I did not want to use a JavaScript lib like http://masonry.desandro.com/
@ -586,8 +492,8 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
* @param ConfigManager $conf Configuration Manager instance. * @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance. * @param PluginManager $pluginManager Plugin Manager instance.
*/ */
function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager) { function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) {
buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager); // Compute list of links to display buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager, $loginManager);
$PAGE->renderPage('linklist'); $PAGE->renderPage('linklist');
} }
@ -607,7 +513,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
read_updates_file($conf->get('resource.updates')), read_updates_file($conf->get('resource.updates')),
$LINKSDB, $LINKSDB,
$conf, $conf,
isLoggedIn() $loginManager->isLoggedIn()
); );
try { try {
$newUpdates = $updater->update(); $newUpdates = $updater->update();
@ -622,18 +528,18 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
die($e->getMessage()); die($e->getMessage());
} }
$PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken()); $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn());
$PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB)); $PAGE->assign('privateLinkcount', count_private($LINKSDB));
$PAGE->assign('plugin_errors', $pluginManager->getErrors()); $PAGE->assign('plugin_errors', $pluginManager->getErrors());
// Determine which page will be rendered. // Determine which page will be rendered.
$query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
$targetPage = Router::findPage($query, $_GET, isLoggedIn()); $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn());
if ( if (
// if the user isn't logged in // if the user isn't logged in
!isLoggedIn() && !$loginManager->isLoggedIn() &&
// and Shaarli doesn't have public content... // and Shaarli doesn't have public content...
$conf->get('privacy.hide_public_links') && $conf->get('privacy.hide_public_links') &&
// and is configured to enforce the login // and is configured to enforce the login
@ -661,7 +567,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$pluginManager->executeHooks('render_' . $name, $plugin_data, $pluginManager->executeHooks('render_' . $name, $plugin_data,
array( array(
'target' => $targetPage, 'target' => $targetPage,
'loggedin' => isLoggedIn() 'loggedin' => $loginManager->isLoggedIn()
) )
); );
$PAGE->assign('plugins_' . $name, $plugin_data); $PAGE->assign('plugins_' . $name, $plugin_data);
@ -686,7 +592,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout'))
{ {
invalidateCaches($conf->get('resource.page_cache')); invalidateCaches($conf->get('resource.page_cache'));
logout(); $sessionManager->logout();
setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH);
header('Location: ?'); header('Location: ?');
exit; exit;
} }
@ -713,7 +620,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$data = array( $data = array(
'linksToDisplay' => $linksToDisplay, 'linksToDisplay' => $linksToDisplay,
); );
$pluginManager->executeHooks('render_picwall', $data, array('loggedin' => isLoggedIn())); $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$PAGE->assign($key, $value); $PAGE->assign($key, $value);
@ -760,7 +667,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
'search_tags' => $searchTags, 'search_tags' => $searchTags,
'tags' => $tagList, 'tags' => $tagList,
); );
$pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn())); $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$PAGE->assign($key, $value); $PAGE->assign($key, $value);
@ -793,7 +700,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
'search_tags' => $searchTags, 'search_tags' => $searchTags,
'tags' => $tags, 'tags' => $tags,
]; ];
$pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]); $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => $loginManager->isLoggedIn()]);
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$PAGE->assign($key, $value); $PAGE->assign($key, $value);
@ -807,7 +714,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
// Daily page. // Daily page.
if ($targetPage == Router::$PAGE_DAILY) { if ($targetPage == Router::$PAGE_DAILY) {
showDaily($PAGE, $LINKSDB, $conf, $pluginManager); showDaily($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
} }
// ATOM and RSS feed. // ATOM and RSS feed.
@ -820,7 +727,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$cache = new CachedPage( $cache = new CachedPage(
$conf->get('resource.page_cache'), $conf->get('resource.page_cache'),
page_url($_SERVER), page_url($_SERVER),
startsWith($query,'do='. $targetPage) && !isLoggedIn() startsWith($query,'do='. $targetPage) && !$loginManager->isLoggedIn()
); );
$cached = $cache->cachedVersion(); $cached = $cache->cachedVersion();
if (!empty($cached)) { if (!empty($cached)) {
@ -829,15 +736,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
} }
// Generate data. // Generate data.
$feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn()); $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, $loginManager->isLoggedIn());
$feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
$feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !isLoggedIn()); $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn());
$feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks'));
$data = $feedGenerator->buildData(); $data = $feedGenerator->buildData();
// Process plugin hook. // Process plugin hook.
$pluginManager->executeHooks('render_feed', $data, array( $pluginManager->executeHooks('render_feed', $data, array(
'loggedin' => isLoggedIn(), 'loggedin' => $loginManager->isLoggedIn(),
'target' => $targetPage, 'target' => $targetPage,
)); ));
@ -985,7 +892,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
} }
// -------- Handle other actions allowed for non-logged in users: // -------- Handle other actions allowed for non-logged in users:
if (!isLoggedIn()) if (!$loginManager->isLoggedIn())
{ {
// User tries to post new link but is not logged in: // User tries to post new link but is not logged in:
// Show login screen, then redirect to ?post=... // Show login screen, then redirect to ?post=...
@ -1001,7 +908,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
exit; exit;
} }
showLinkList($PAGE, $LINKSDB, $conf, $pluginManager); showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
if (isset($_GET['edit_link'])) { if (isset($_GET['edit_link'])) {
header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
exit; exit;
@ -1052,7 +959,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
$conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt'))); $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt')));
try { try {
$conf->write(isLoggedIn()); $conf->write($loginManager->isLoggedIn());
} }
catch(Exception $e) { catch(Exception $e) {
error_log( error_log(
@ -1103,7 +1010,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
$conf->set('translation.language', escape($_POST['language'])); $conf->set('translation.language', escape($_POST['language']));
try { try {
$conf->write(isLoggedIn()); $conf->write($loginManager->isLoggedIn());
$history->updateSettings(); $history->updateSettings();
invalidateCaches($conf->get('resource.page_cache')); invalidateCaches($conf->get('resource.page_cache'));
} }
@ -1555,7 +1462,7 @@ function($a, $b) { return $a['order'] - $b['order']; }
else { else {
$conf->set('general.enabled_plugins', save_plugin_config($_POST)); $conf->set('general.enabled_plugins', save_plugin_config($_POST));
} }
$conf->write(isLoggedIn()); $conf->write($loginManager->isLoggedIn());
$history->updateSettings(); $history->updateSettings();
} }
catch (Exception $e) { catch (Exception $e) {
@ -1580,7 +1487,7 @@ function($a, $b) { return $a['order'] - $b['order']; }
} }
// -------- Otherwise, simply display search form and links: // -------- Otherwise, simply display search form and links:
showLinkList($PAGE, $LINKSDB, $conf, $pluginManager); showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
exit; exit;
} }
@ -1592,8 +1499,9 @@ function($a, $b) { return $a['order'] - $b['order']; }
* @param LinkDB $LINKSDB LinkDB instance. * @param LinkDB $LINKSDB LinkDB instance.
* @param ConfigManager $conf Configuration Manager instance. * @param ConfigManager $conf Configuration Manager instance.
* @param PluginManager $pluginManager Plugin Manager instance. * @param PluginManager $pluginManager Plugin Manager instance.
* @param LoginManager $loginManager LoginManager instance
*/ */
function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager) function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
{ {
// Used in templates // Used in templates
if (isset($_GET['searchtags'])) { if (isset($_GET['searchtags'])) {
@ -1632,8 +1540,6 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
$keys[] = $key; $keys[] = $key;
} }
// Select articles according to paging. // Select articles according to paging.
$pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']);
$pagecount = $pagecount == 0 ? 1 : $pagecount; $pagecount = $pagecount == 0 ? 1 : $pagecount;
@ -1714,7 +1620,7 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
$data['pagetitle'] .= '- '. $conf->get('general.title'); $data['pagetitle'] .= '- '. $conf->get('general.title');
} }
$pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => $loginManager->isLoggedIn()));
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$PAGE->assign($key, $value); $PAGE->assign($key, $value);
@ -1985,7 +1891,7 @@ function install($conf, $sessionManager) {
); );
try { try {
// Everything is ok, let's create config file. // Everything is ok, let's create config file.
$conf->write(isLoggedIn()); $conf->write($loginManager->isLoggedIn());
} }
catch(Exception $e) { catch(Exception $e) {
error_log( error_log(
@ -2249,7 +2155,7 @@ function resizeImage($filepath)
$linkDb = new LinkDB( $linkDb = new LinkDB(
$conf->get('resource.datastore'), $conf->get('resource.datastore'),
isLoggedIn(), $loginManager->isLoggedIn(),
$conf->get('privacy.hide_public_links'), $conf->get('privacy.hide_public_links'),
$conf->get('redirector.url'), $conf->get('redirector.url'),
$conf->get('redirector.encode_url') $conf->get('redirector.encode_url')

View file

@ -0,0 +1,52 @@
<?php
/**
* HttpUtils' tests
*/
require_once 'application/HttpUtils.php';
/**
* Unitary tests for client_ip_id()
*/
class ClientIpIdTest extends PHPUnit_Framework_TestCase
{
/**
* Get a remote client ID based on its IP
*/
public function testClientIpIdRemote()
{
$this->assertEquals(
'10.1.167.42',
client_ip_id(['REMOTE_ADDR' => '10.1.167.42'])
);
}
/**
* Get a remote client ID based on its IP and proxy information (1)
*/
public function testClientIpIdRemoteForwarded()
{
$this->assertEquals(
'10.1.167.42_127.0.1.47',
client_ip_id([
'REMOTE_ADDR' => '10.1.167.42',
'HTTP_X_FORWARDED_FOR' => '127.0.1.47'
])
);
}
/**
* Get a remote client ID based on its IP and proxy information (2)
*/
public function testClientIpIdRemoteForwardedClient()
{
$this->assertEquals(
'10.1.167.42_10.1.167.56_127.0.1.47',
client_ip_id([
'REMOTE_ADDR' => '10.1.167.42',
'HTTP_X_FORWARDED_FOR' => '10.1.167.56',
'HTTP_CLIENT_IP' => '127.0.1.47'
])
);
}
}

View file

@ -1,149 +0,0 @@
<?php
require_once 'tests/utils/FakeConfigManager.php';
// Initialize reference data _before_ PHPUnit starts a session
require_once 'tests/utils/ReferenceSessionIdHashes.php';
ReferenceSessionIdHashes::genAllHashes();
use \Shaarli\SessionManager;
use \PHPUnit\Framework\TestCase;
/**
* Test coverage for SessionManager
*/
class SessionManagerTest extends TestCase
{
// Session ID hashes
protected static $sidHashes = null;
// Fake ConfigManager
protected static $conf = null;
/**
* Assign reference data
*/
public static function setUpBeforeClass()
{
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
self::$conf = new FakeConfigManager();
}
/**
* Generate a session token
*/
public function testGenerateToken()
{
$session = [];
$sessionManager = new SessionManager($session, self::$conf);
$token = $sessionManager->generateToken();
$this->assertEquals(1, $session['tokens'][$token]);
$this->assertEquals(40, strlen($token));
}
/**
* Check a session token
*/
public function testCheckToken()
{
$token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
$session = [
'tokens' => [
$token => 1,
],
];
$sessionManager = new SessionManager($session, self::$conf);
// check and destroy the token
$this->assertTrue($sessionManager->checkToken($token));
$this->assertFalse(isset($session['tokens'][$token]));
// ensure the token has been destroyed
$this->assertFalse($sessionManager->checkToken($token));
}
/**
* Generate and check a session token
*/
public function testGenerateAndCheckToken()
{
$session = [];
$sessionManager = new SessionManager($session, self::$conf);
$token = $sessionManager->generateToken();
// ensure a token has been generated
$this->assertEquals(1, $session['tokens'][$token]);
$this->assertEquals(40, strlen($token));
// check and destroy the token
$this->assertTrue($sessionManager->checkToken($token));
$this->assertFalse(isset($session['tokens'][$token]));
// ensure the token has been destroyed
$this->assertFalse($sessionManager->checkToken($token));
}
/**
* Check an invalid session token
*/
public function testCheckInvalidToken()
{
$session = [];
$sessionManager = new SessionManager($session, self::$conf);
$this->assertFalse($sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
}
/**
* Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
*
* This tests extensively covers all hash algorithms / bit representations
*/
public function testIsAnyHashSessionIdValid()
{
foreach (self::$sidHashes as $algo => $bpcs) {
foreach ($bpcs as $bpc => $hash) {
$this->assertTrue(SessionManager::checkId($hash));
}
}
}
/**
* Test checkId with a valid ID - SHA-1 hashes
*/
public function testIsSha1SessionIdValid()
{
$this->assertTrue(SessionManager::checkId(sha1('shaarli')));
}
/**
* Test checkId with a valid ID - SHA-256 hashes
*/
public function testIsSha256SessionIdValid()
{
$this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
}
/**
* Test checkId with a valid ID - SHA-512 hashes
*/
public function testIsSha512SessionIdValid()
{
$this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
}
/**
* Test checkId with invalid IDs.
*/
public function testIsSessionIdInvalid()
{
$this->assertFalse(SessionManager::checkId(''));
$this->assertFalse(SessionManager::checkId([]));
$this->assertFalse(
SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
);
}
}

View file

@ -1,5 +1,5 @@
<?php <?php
namespace Shaarli; namespace Shaarli\Security;
require_once 'tests/utils/FakeConfigManager.php'; require_once 'tests/utils/FakeConfigManager.php';
use \PHPUnit\Framework\TestCase; use \PHPUnit\Framework\TestCase;
@ -9,15 +9,54 @@
*/ */
class LoginManagerTest extends TestCase class LoginManagerTest extends TestCase
{ {
/** @var \FakeConfigManager Configuration Manager instance */
protected $configManager = null; protected $configManager = null;
/** @var LoginManager Login Manager instance */
protected $loginManager = null; protected $loginManager = null;
/** @var SessionManager Session Manager instance */
protected $sessionManager = null;
/** @var string Banned IP filename */
protected $banFile = 'sandbox/ipbans.php'; protected $banFile = 'sandbox/ipbans.php';
/** @var string Log filename */
protected $logFile = 'sandbox/shaarli.log'; protected $logFile = 'sandbox/shaarli.log';
/** @var array Simulates the $_COOKIE array */
protected $cookie = [];
/** @var array Simulates the $GLOBALS array */
protected $globals = []; protected $globals = [];
protected $ipAddr = '127.0.0.1';
/** @var array Simulates the $_SERVER array */
protected $server = []; protected $server = [];
/** @var array Simulates the $_SESSION array */
protected $session = [];
/** @var string Advertised client IP address */
protected $clientIpAddress = '10.1.47.179';
/** @var string Local client IP address */
protected $ipAddr = '127.0.0.1';
/** @var string Trusted proxy IP address */
protected $trustedProxy = '10.1.1.100'; protected $trustedProxy = '10.1.1.100';
/** @var string User login */
protected $login = 'johndoe';
/** @var string User password */
protected $password = 'IC4nHazL0g1n?';
/** @var string Hash of the salted user password */
protected $passwordHash = '';
/** @var string Salt used by hash functions */
protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2';
/** /**
* Prepare or reset test resources * Prepare or reset test resources
*/ */
@ -27,7 +66,12 @@ public function setUp()
unlink($this->banFile); unlink($this->banFile);
} }
$this->passwordHash = sha1($this->password . $this->login . $this->salt);
$this->configManager = new \FakeConfigManager([ $this->configManager = new \FakeConfigManager([
'credentials.login' => $this->login,
'credentials.hash' => $this->passwordHash,
'credentials.salt' => $this->salt,
'resource.ban_file' => $this->banFile, 'resource.ban_file' => $this->banFile,
'resource.log' => $this->logFile, 'resource.log' => $this->logFile,
'security.ban_after' => 4, 'security.ban_after' => 4,
@ -35,10 +79,15 @@ public function setUp()
'security.trusted_proxies' => [$this->trustedProxy], 'security.trusted_proxies' => [$this->trustedProxy],
]); ]);
$this->cookie = [];
$this->globals = &$GLOBALS; $this->globals = &$GLOBALS;
unset($this->globals['IPBANS']); unset($this->globals['IPBANS']);
$this->loginManager = new LoginManager($this->globals, $this->configManager); $this->session = [];
$this->sessionManager = new SessionManager($this->session, $this->configManager);
$this->loginManager = new LoginManager($this->globals, $this->configManager, $this->sessionManager);
$this->server['REMOTE_ADDR'] = $this->ipAddr; $this->server['REMOTE_ADDR'] = $this->ipAddr;
} }
@ -59,7 +108,7 @@ public function testReadBanFile()
$this->banFile, $this->banFile,
"<?php\n\$GLOBALS['IPBANS']=array('FAILURES' => array('127.0.0.1' => 99));\n?>" "<?php\n\$GLOBALS['IPBANS']=array('FAILURES' => array('127.0.0.1' => 99));\n?>"
); );
new LoginManager($this->globals, $this->configManager); new LoginManager($this->globals, $this->configManager, null);
$this->assertEquals(99, $this->globals['IPBANS']['FAILURES']['127.0.0.1']); $this->assertEquals(99, $this->globals['IPBANS']['FAILURES']['127.0.0.1']);
} }
@ -196,4 +245,130 @@ public function testCanLoginIpBanExpired()
$this->globals['IPBANS']['BANS'][$this->ipAddr] = time() - 3600; $this->globals['IPBANS']['BANS'][$this->ipAddr] = time() - 3600;
$this->assertTrue($this->loginManager->canLogin($this->server)); $this->assertTrue($this->loginManager->canLogin($this->server));
} }
/**
* Generate a token depending on the user credentials and client IP
*/
public function testGenerateStaySignedInToken()
{
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
$this->assertEquals(
sha1($this->passwordHash . $this->clientIpAddress . $this->salt),
$this->loginManager->getStaySignedInToken()
);
}
/**
* Check user login - Shaarli has not yet been configured
*/
public function testCheckLoginStateNotConfigured()
{
$configManager = new \FakeConfigManager([
'resource.ban_file' => $this->banFile,
]);
$loginManager = new LoginManager($this->globals, $configManager, null);
$loginManager->checkLoginState([], '');
$this->assertFalse($loginManager->isLoggedIn());
}
/**
* Check user login - the client cookie does not match the server token
*/
public function testCheckLoginStateStaySignedInWithInvalidToken()
{
// simulate a previous login
$this->session = [
'ip' => $this->clientIpAddress,
'expires_on' => time() + 100,
];
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
$this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = 'nope';
$this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
$this->assertTrue($this->loginManager->isLoggedIn());
$this->assertTrue(empty($this->session['username']));
}
/**
* Check user login - the client cookie matches the server token
*/
public function testCheckLoginStateStaySignedInWithValidToken()
{
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
$this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = $this->loginManager->getStaySignedInToken();
$this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
$this->assertTrue($this->loginManager->isLoggedIn());
$this->assertEquals($this->login, $this->session['username']);
$this->assertEquals($this->clientIpAddress, $this->session['ip']);
}
/**
* Check user login - the session has expired
*/
public function testCheckLoginStateSessionExpired()
{
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
$this->session['expires_on'] = time() - 100;
$this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
$this->assertFalse($this->loginManager->isLoggedIn());
}
/**
* Check user login - the remote client IP has changed
*/
public function testCheckLoginStateClientIpChanged()
{
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
$this->loginManager->checkLoginState($this->cookie, '10.7.157.98');
$this->assertFalse($this->loginManager->isLoggedIn());
}
/**
* Check user credentials - wrong login supplied
*/
public function testCheckCredentialsWrongLogin()
{
$this->assertFalse(
$this->loginManager->checkCredentials('', '', 'b4dl0g1n', $this->password)
);
}
/**
* Check user credentials - wrong password supplied
*/
public function testCheckCredentialsWrongPassword()
{
$this->assertFalse(
$this->loginManager->checkCredentials('', '', $this->login, 'b4dp455wd')
);
}
/**
* Check user credentials - wrong login and password supplied
*/
public function testCheckCredentialsWrongLoginAndPassword()
{
$this->assertFalse(
$this->loginManager->checkCredentials('', '', 'b4dl0g1n', 'b4dp455wd')
);
}
/**
* Check user credentials - correct login and password supplied
*/
public function testCheckCredentialsGoodLoginAndPassword()
{
$this->assertTrue(
$this->loginManager->checkCredentials('', '', $this->login, $this->password)
);
}
} }

View file

@ -0,0 +1,273 @@
<?php
require_once 'tests/utils/FakeConfigManager.php';
// Initialize reference data _before_ PHPUnit starts a session
require_once 'tests/utils/ReferenceSessionIdHashes.php';
ReferenceSessionIdHashes::genAllHashes();
use \Shaarli\Security\SessionManager;
use \PHPUnit\Framework\TestCase;
/**
* Test coverage for SessionManager
*/
class SessionManagerTest extends TestCase
{
/** @var array Session ID hashes */
protected static $sidHashes = null;
/** @var \FakeConfigManager ConfigManager substitute for testing */
protected $conf = null;
/** @var array $_SESSION array for testing */
protected $session = [];
/** @var SessionManager Server-side session management abstraction */
protected $sessionManager = null;
/**
* Assign reference data
*/
public static function setUpBeforeClass()
{
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
}
/**
* Initialize or reset test resources
*/
public function setUp()
{
$this->conf = new FakeConfigManager([
'credentials.login' => 'johndoe',
'credentials.salt' => 'salt',
'security.session_protection_disabled' => false,
]);
$this->session = [];
$this->sessionManager = new SessionManager($this->session, $this->conf);
}
/**
* Generate a session token
*/
public function testGenerateToken()
{
$token = $this->sessionManager->generateToken();
$this->assertEquals(1, $this->session['tokens'][$token]);
$this->assertEquals(40, strlen($token));
}
/**
* Check a session token
*/
public function testCheckToken()
{
$token = '4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b';
$session = [
'tokens' => [
$token => 1,
],
];
$sessionManager = new SessionManager($session, $this->conf);
// check and destroy the token
$this->assertTrue($sessionManager->checkToken($token));
$this->assertFalse(isset($session['tokens'][$token]));
// ensure the token has been destroyed
$this->assertFalse($sessionManager->checkToken($token));
}
/**
* Generate and check a session token
*/
public function testGenerateAndCheckToken()
{
$token = $this->sessionManager->generateToken();
// ensure a token has been generated
$this->assertEquals(1, $this->session['tokens'][$token]);
$this->assertEquals(40, strlen($token));
// check and destroy the token
$this->assertTrue($this->sessionManager->checkToken($token));
$this->assertFalse(isset($this->session['tokens'][$token]));
// ensure the token has been destroyed
$this->assertFalse($this->sessionManager->checkToken($token));
}
/**
* Check an invalid session token
*/
public function testCheckInvalidToken()
{
$this->assertFalse($this->sessionManager->checkToken('4dccc3a45ad9d03e5542b90c37d8db6d10f2b38b'));
}
/**
* Test SessionManager::checkId with a valid ID - TEST ALL THE HASHES!
*
* This tests extensively covers all hash algorithms / bit representations
*/
public function testIsAnyHashSessionIdValid()
{
foreach (self::$sidHashes as $algo => $bpcs) {
foreach ($bpcs as $bpc => $hash) {
$this->assertTrue(SessionManager::checkId($hash));
}
}
}
/**
* Test checkId with a valid ID - SHA-1 hashes
*/
public function testIsSha1SessionIdValid()
{
$this->assertTrue(SessionManager::checkId(sha1('shaarli')));
}
/**
* Test checkId with a valid ID - SHA-256 hashes
*/
public function testIsSha256SessionIdValid()
{
$this->assertTrue(SessionManager::checkId(hash('sha256', 'shaarli')));
}
/**
* Test checkId with a valid ID - SHA-512 hashes
*/
public function testIsSha512SessionIdValid()
{
$this->assertTrue(SessionManager::checkId(hash('sha512', 'shaarli')));
}
/**
* Test checkId with invalid IDs.
*/
public function testIsSessionIdInvalid()
{
$this->assertFalse(SessionManager::checkId(''));
$this->assertFalse(SessionManager::checkId([]));
$this->assertFalse(
SessionManager::checkId('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
);
}
/**
* Store login information after a successful login
*/
public function testStoreLoginInfo()
{
$this->sessionManager->storeLoginInfo('ip_id');
$this->assertGreaterThan(time(), $this->session['expires_on']);
$this->assertEquals('ip_id', $this->session['ip']);
$this->assertEquals('johndoe', $this->session['username']);
}
/**
* Extend a server-side session by SessionManager::$SHORT_TIMEOUT
*/
public function testExtendSession()
{
$this->sessionManager->extendSession();
$this->assertGreaterThan(time(), $this->session['expires_on']);
$this->assertLessThanOrEqual(
time() + SessionManager::$SHORT_TIMEOUT,
$this->session['expires_on']
);
}
/**
* Extend a server-side session by SessionManager::$LONG_TIMEOUT
*/
public function testExtendSessionStaySignedIn()
{
$this->sessionManager->setStaySignedIn(true);
$this->sessionManager->extendSession();
$this->assertGreaterThan(time(), $this->session['expires_on']);
$this->assertGreaterThan(
time() + SessionManager::$LONG_TIMEOUT - 10,
$this->session['expires_on']
);
$this->assertLessThanOrEqual(
time() + SessionManager::$LONG_TIMEOUT,
$this->session['expires_on']
);
}
/**
* Unset session variables after logging out
*/
public function testLogout()
{
$this->session = [
'ip' => 'ip_id',
'expires_on' => time() + 1000,
'username' => 'johndoe',
'visibility' => 'public',
'untaggedonly' => false,
];
$this->sessionManager->logout();
$this->assertFalse(isset($this->session['ip']));
$this->assertFalse(isset($this->session['expires_on']));
$this->assertFalse(isset($this->session['username']));
$this->assertFalse(isset($this->session['visibility']));
$this->assertFalse(isset($this->session['untaggedonly']));
}
/**
* The session is active and expiration time has been reached
*/
public function testHasExpiredTimeElapsed()
{
$this->session['expires_on'] = time() - 10;
$this->assertTrue($this->sessionManager->hasSessionExpired());
}
/**
* The session is active and expiration time has not been reached
*/
public function testHasNotExpired()
{
$this->session['expires_on'] = time() + 1000;
$this->assertFalse($this->sessionManager->hasSessionExpired());
}
/**
* Session hijacking protection is disabled, we assume the IP has not changed
*/
public function testHasClientIpChangedNoSessionProtection()
{
$this->conf->set('security.session_protection_disabled', true);
$this->assertFalse($this->sessionManager->hasClientIpChanged(''));
}
/**
* The client IP identifier has not changed
*/
public function testHasClientIpChangedNope()
{
$this->session['ip'] = 'ip_id';
$this->assertFalse($this->sessionManager->hasClientIpChanged('ip_id'));
}
/**
* The client IP identifier has changed
*/
public function testHasClientIpChanged()
{
$this->session['ip'] = 'ip_id_one';
$this->assertTrue($this->sessionManager->hasClientIpChanged('ip_id_two'));
}
}

View file

@ -42,4 +42,16 @@ public function get($key)
} }
return $key; return $key;
} }
/**
* Check if a setting exists
*
* @param string $setting Asked setting, keys separated with dots
*
* @return bool true if the setting exists, false otherwise
*/
public function exists($setting)
{
return array_key_exists($setting, $this->values);
}
} }

View file

@ -136,7 +136,7 @@
<div class="linklist-item-thumbnail">{$thumb}</div> <div class="linklist-item-thumbnail">{$thumb}</div>
{/if} {/if}
{if="isLoggedIn()"} {if="$is_logged_in"}
<div class="linklist-item-editbuttons"> <div class="linklist-item-editbuttons">
{if="$value.private"} {if="$value.private"}
<span class="label label-private">{$strPrivate}</span> <span class="label label-private">{$strPrivate}</span>
@ -179,7 +179,7 @@ <h2>
<div class="linklist-item-infos-date-url-block pure-g"> <div class="linklist-item-infos-date-url-block pure-g">
<div class="linklist-item-infos-dateblock pure-u-lg-7-12 pure-u-1"> <div class="linklist-item-infos-dateblock pure-u-lg-7-12 pure-u-1">
{if="isLoggedIn()"} {if="$is_logged_in"}
<div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible"> <div class="linklist-item-infos-controls-group pure-u-0 pure-u-lg-visible">
<span class="linklist-item-infos-controls-item ctrl-checkbox"> <span class="linklist-item-infos-controls-item ctrl-checkbox">
<input type="checkbox" class="delete-checkbox" value="{$value.id}"> <input type="checkbox" class="delete-checkbox" value="{$value.id}">
@ -196,7 +196,7 @@ <h2>
</div> </div>
{/if} {/if}
<a href="?{$value.shorturl}" title="{$strPermalink}"> <a href="?{$value.shorturl}" title="{$strPermalink}">
{if="!$hide_timestamps || isLoggedIn()"} {if="!$hide_timestamps || $is_logged_in"}
{$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink} {$updated=$value.updated_timestamp ? $strEdited. format_date($value.updated) : $strPermalink}
<span class="linkdate" title="{$updated}"> <span class="linkdate" title="{$updated}">
<i class="fa fa-clock-o"></i> <i class="fa fa-clock-o"></i>
@ -236,7 +236,7 @@ <h2>
{if="$link_plugin_counter - 1 != $counter"}&middot;{/if} {if="$link_plugin_counter - 1 != $counter"}&middot;{/if}
{/loop} {/loop}
{/if} {/if}
{if="isLoggedIn()"} {if="$is_logged_in"}
&middot; &middot;
<a href="?delete_link&amp;lf_linkdate={$value.id}&amp;token={$token}" <a href="?delete_link&amp;lf_linkdate={$value.id}&amp;token={$token}"
title="{$strDelete}" class="delete-link confirm-delete"> title="{$strDelete}" class="delete-link confirm-delete">

View file

@ -1,11 +1,11 @@
<div class="linklist-paging"> <div class="linklist-paging">
<div class="paging pure-g"> <div class="paging pure-g">
<div class="linklist-filters pure-u-1-3"> <div class="linklist-filters pure-u-1-3">
{if="isLoggedIn() or !empty($action_plugin)"} {if="$is_logged_in or !empty($action_plugin)"}
<span class="linklist-filters-text pure-u-0 pure-u-lg-visible"> <span class="linklist-filters-text pure-u-0 pure-u-lg-visible">
{'Filters'|t} {'Filters'|t}
</span> </span>
{if="isLoggedIn()"} {if="$is_logged_in"}
<a href="?visibility=private" title="{'Only display private links'|t}" <a href="?visibility=private" title="{'Only display private links'|t}"
class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}" class="{if="$visibility==='private'"}filter-on{else}filter-off{/if}"
><i class="fa fa-user-secret"></i></a> ><i class="fa fa-user-secret"></i></a>

View file

@ -4,7 +4,7 @@
<div class="pure-u-2-24"></div> <div class="pure-u-2-24"></div>
<div id="footer" class="pure-u-20-24 footer-container"> <div id="footer" class="pure-u-20-24 footer-container">
<strong><a href="https://github.com/shaarli/Shaarli">Shaarli</a></strong> <strong><a href="https://github.com/shaarli/Shaarli">Shaarli</a></strong>
{if="isLoggedIn()===true"} {if="$is_logged_in===true"}
{$version} {$version}
{/if} {/if}
&middot; &middot;

View file

@ -17,7 +17,7 @@
{$shaarlititle} {$shaarlititle}
</a> </a>
</li> </li>
{if="isLoggedIn() || $openshaarli"} {if="$is_logged_in || $openshaarli"}
<li class="pure-menu-item"> <li class="pure-menu-item">
<a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare"> <a href="?do=addlink" class="pure-menu-link" id="shaarli-menu-shaare">
<i class="fa fa-plus" ></i> {'Shaare'|t} <i class="fa fa-plus" ></i> {'Shaare'|t}
@ -50,7 +50,7 @@
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss"> <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-rss">
<a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a> <a href="?do={$feed_type}{$searchcrits}" class="pure-menu-link">{'RSS Feed'|t}</a>
</li> </li>
{if="isLoggedIn()"} {if="$is_logged_in"}
<li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout"> <li class="pure-menu-item pure-u-lg-0 shaarli-menu-mobile" id="shaarli-menu-mobile-logout">
<a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a> <a href="?do=logout" class="pure-menu-link">{'Logout'|t}</a>
</li> </li>
@ -74,7 +74,7 @@
<i class="fa fa-rss"></i> <i class="fa fa-rss"></i>
</a> </a>
</li> </li>
{if="!isLoggedIn()"} {if="!$is_logged_in"}
<li class="pure-menu-item" id="shaarli-menu-desktop-login"> <li class="pure-menu-item" id="shaarli-menu-desktop-login">
<a href="?do=login" class="pure-menu-link" <a href="?do=login" class="pure-menu-link"
data-open-id="header-login-form" data-open-id="header-login-form"
@ -120,7 +120,7 @@
</div> </div>
</div> </div>
</div> </div>
{if="!isLoggedIn()"} {if="!$is_logged_in"}
<form method="post" name="loginform"> <form method="post" name="loginform">
<div class="subheader-form header-login-form" id="header-login-form"> <div class="subheader-form header-login-form" id="header-login-form">
<input type="text" name="login" placeholder="{'Username'|t}" tabindex="3"> <input type="text" name="login" placeholder="{'Username'|t}" tabindex="3">
@ -155,7 +155,7 @@
</div> </div>
{/if} {/if}
{if="!empty($plugin_errors) && isLoggedIn()"} {if="!empty($plugin_errors) && $is_logged_in"}
<div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert"> <div class="pure-g new-version-message pure-alert pure-alert-error pure-alert-closable" id="shaarli-errors-alert">
<div class="pure-u-2-24"></div> <div class="pure-u-2-24"></div>
<div class="pure-u-20-24"> <div class="pure-u-20-24">

View file

@ -49,7 +49,7 @@ <h2 class="window-title">{'Tag list'|t} - {$countTags} {'tags'|t}</h2>
{loop="tags"} {loop="tags"}
<div class="tag-list-item pure-g" data-tag="{$key}"> <div class="tag-list-item pure-g" data-tag="{$key}">
<div class="pure-u-1"> <div class="pure-u-1">
{if="isLoggedIn()===true"} {if="$is_logged_in===true"}
<a href="#" class="delete-tag"><i class="fa fa-trash"></i></a>&nbsp;&nbsp; <a href="#" class="delete-tag"><i class="fa fa-trash"></i></a>&nbsp;&nbsp;
<a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag"> <a href="?do=changetag&fromtag={$key|urlencode}" class="rename-tag">
<i class="fa fa-pencil-square-o {$key}"></i> <i class="fa fa-pencil-square-o {$key}"></i>
@ -63,7 +63,7 @@ <h2 class="window-title">{'Tag list'|t} - {$countTags} {'tags'|t}</h2>
{$value} {$value}
{/loop} {/loop}
</div> </div>
{if="isLoggedIn()===true"} {if="$is_logged_in===true"}
<div class="rename-tag-form pure-u-1"> <div class="rename-tag-form pure-u-1">
<input type="text" name="{$key}" value="{$key}" class="rename-tag-input" /> <input type="text" name="{$key}" value="{$key}" class="rename-tag-input" />
<a href="#" class="validate-rename-tag"><i class="fa fa-check"></i></a> <a href="#" class="validate-rename-tag"><i class="fa fa-check"></i></a>
@ -81,7 +81,7 @@ <h2 class="window-title">{'Tag list'|t} - {$countTags} {'tags'|t}</h2>
</div> </div>
</div> </div>
{if="isLoggedIn()===true"} {if="$is_logged_in===true"}
<input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}" <input type="hidden" name="taglist" value="{loop="$tags"}{$key} {/loop}"
{/if} {/if}

View file

@ -53,7 +53,7 @@
<img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink"> <img src="img/squiggle.png" width="25" height="26" title="permalink" alt="permalink">
</a> </a>
</div> </div>
{if="!$hide_timestamps || isLoggedIn()"} {if="!$hide_timestamps || $is_logged_in"}
<div class="dailyEntryLinkdate"> <div class="dailyEntryLinkdate">
<a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a> <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
</div> </div>

View file

@ -82,7 +82,7 @@
<a id="{$value.shorturl}"></a> <a id="{$value.shorturl}"></a>
<div class="thumbnail">{$value.url|thumbnail}</div> <div class="thumbnail">{$value.url|thumbnail}</div>
<div class="linkcontainer"> <div class="linkcontainer">
{if="isLoggedIn()"} {if="$is_logged_in"}
<div class="linkeditbuttons"> <div class="linkeditbuttons">
<form method="GET" class="buttoneditform"> <form method="GET" class="buttoneditform">
<input type="hidden" name="edit_link" value="{$value.id}"> <input type="hidden" name="edit_link" value="{$value.id}">
@ -102,7 +102,7 @@
</span> </span>
<br> <br>
{if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if} {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
{if="!$hide_timestamps || isLoggedIn()"} {if="!$hide_timestamps || $is_logged_in"}
{$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'} {$updated=$value.updated_timestamp ? 'Edited: '. format_date($value.updated) : 'Permalink'}
<span class="linkdate" title="Permalink"> <span class="linkdate" title="Permalink">
<a href="?{$value.shorturl}"> <a href="?{$value.shorturl}">

View file

@ -1,5 +1,5 @@
<div class="paging"> <div class="paging">
{if="isLoggedIn()"} {if="$is_logged_in"}
<div class="paging_privatelinks"> <div class="paging_privatelinks">
<a href="?visibility=private"> <a href="?visibility=private">
{if="$visibility=='private'"} {if="$visibility=='private'"}

View file

@ -25,7 +25,7 @@
<script src="js/shaarli.min.js"></script> <script src="js/shaarli.min.js"></script>
{if="isLoggedIn()"} {if="$is_logged_in"}
<script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script> <script>function confirmDeleteLink() { var agree=confirm("Are you sure you want to delete this link ?"); if (agree) return true ; else return false ; }</script>
{/if} {/if}

View file

@ -17,7 +17,7 @@
{ignore} When called as a popup from bookmarklet, do not display menu. {/ignore} {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore}
{else} {else}
<li><a href="{$titleLink}" class="nomobile">Home</a></li> <li><a href="{$titleLink}" class="nomobile">Home</a></li>
{if="isLoggedIn()"} {if="$is_logged_in"}
<li><a href="?do=logout">Logout</a></li> <li><a href="?do=logout">Logout</a></li>
<li><a href="?do=tools">Tools</a></li> <li><a href="?do=tools">Tools</a></li>
<li><a href="?do=addlink">Add link</a></li> <li><a href="?do=addlink">Add link</a></li>
@ -46,7 +46,7 @@
</ul> </ul>
</div> </div>
{if="!empty($plugin_errors) && isLoggedIn()"} {if="!empty($plugin_errors) && $is_logged_in"}
<ul class="errors"> <ul class="errors">
{loop="$plugin_errors"} {loop="$plugin_errors"}
<li>{$value}</li> <li>{$value}</li>