Merge pull request #1601 from ArthurHoaro/feature/psr3

This commit is contained in:
ArthurHoaro 2020-10-24 11:37:29 +02:00 committed by GitHub
commit 820cae27cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 172 additions and 125 deletions

View file

@ -4,21 +4,23 @@
*/ */
/** /**
* Logs a message to a text file * Format log using provided data.
* *
* The log format is compatible with fail2ban. * @param string $message the message to log
* @param string|null $clientIp the client's remote IPv4/IPv6 address
* *
* @param string $logFile where to write the logs * @return string Formatted message to log
* @param string $clientIp the client's remote IPv4/IPv6 address
* @param string $message the message to log
*/ */
function logm($logFile, $clientIp, $message) function format_log(string $message, string $clientIp = null): string
{ {
file_put_contents( $out = $message;
$logFile,
date('Y/m/d H:i:s').' - '.$clientIp.' - '.strval($message).PHP_EOL, if (!empty($clientIp)) {
FILE_APPEND // Note: we keep the first dash to avoid breaking fail2ban configs
); $out = '- ' . $clientIp . ' - ' . $out;
}
return $out;
} }
/** /**

View file

@ -5,6 +5,7 @@
namespace Shaarli\Container; namespace Shaarli\Container;
use malkusch\lock\mutex\FlockMutex; use malkusch\lock\mutex\FlockMutex;
use Psr\Log\LoggerInterface;
use Shaarli\Bookmark\BookmarkFileService; use Shaarli\Bookmark\BookmarkFileService;
use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
@ -49,6 +50,9 @@ class ContainerBuilder
/** @var LoginManager */ /** @var LoginManager */
protected $login; protected $login;
/** @var LoggerInterface */
protected $logger;
/** @var string|null */ /** @var string|null */
protected $basePath = null; protected $basePath = null;
@ -56,12 +60,14 @@ public function __construct(
ConfigManager $conf, ConfigManager $conf,
SessionManager $session, SessionManager $session,
CookieManager $cookieManager, CookieManager $cookieManager,
LoginManager $login LoginManager $login,
LoggerInterface $logger
) { ) {
$this->conf = $conf; $this->conf = $conf;
$this->session = $session; $this->session = $session;
$this->login = $login; $this->login = $login;
$this->cookieManager = $cookieManager; $this->cookieManager = $cookieManager;
$this->logger = $logger;
} }
public function build(): ShaarliContainer public function build(): ShaarliContainer
@ -72,6 +78,7 @@ public function build(): ShaarliContainer
$container['sessionManager'] = $this->session; $container['sessionManager'] = $this->session;
$container['cookieManager'] = $this->cookieManager; $container['cookieManager'] = $this->cookieManager;
$container['loginManager'] = $this->login; $container['loginManager'] = $this->login;
$container['logger'] = $this->logger;
$container['basePath'] = $this->basePath; $container['basePath'] = $this->basePath;
$container['plugins'] = function (ShaarliContainer $container): PluginManager { $container['plugins'] = function (ShaarliContainer $container): PluginManager {
@ -99,6 +106,7 @@ public function build(): ShaarliContainer
return new PageBuilder( return new PageBuilder(
$container->conf, $container->conf,
$container->sessionManager->getSession(), $container->sessionManager->getSession(),
$container->logger,
$container->bookmarkService, $container->bookmarkService,
$container->sessionManager->generateToken(), $container->sessionManager->generateToken(),
$container->loginManager->isLoggedIn() $container->loginManager->isLoggedIn()

View file

@ -4,6 +4,7 @@
namespace Shaarli\Container; namespace Shaarli\Container;
use Psr\Log\LoggerInterface;
use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Feed\FeedBuilder; use Shaarli\Feed\FeedBuilder;
@ -36,6 +37,7 @@
* @property History $history * @property History $history
* @property HttpAccess $httpAccess * @property HttpAccess $httpAccess
* @property LoginManager $loginManager * @property LoginManager $loginManager
* @property LoggerInterface $logger
* @property MetadataRetriever $metadataRetriever * @property MetadataRetriever $metadataRetriever
* @property NetscapeBookmarkUtils $netscapeBookmarkUtils * @property NetscapeBookmarkUtils $netscapeBookmarkUtils
* @property callable $notFoundHandler Overrides default Slim exception display * @property callable $notFoundHandler Overrides default Slim exception display

View file

@ -65,7 +65,6 @@ public function login(Request $request, Response $response): Response
} }
if (!$this->container->loginManager->checkCredentials( if (!$this->container->loginManager->checkCredentials(
$this->container->environment['REMOTE_ADDR'],
client_ip_id($this->container->environment), client_ip_id($this->container->environment),
$request->getParam('login'), $request->getParam('login'),
$request->getParam('password') $request->getParam('password')

View file

@ -3,7 +3,7 @@
namespace Shaarli\Render; namespace Shaarli\Render;
use Exception; use Exception;
use exceptions\MissingBasePathException; use Psr\Log\LoggerInterface;
use RainTPL; use RainTPL;
use Shaarli\ApplicationUtils; use Shaarli\ApplicationUtils;
use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Bookmark\BookmarkServiceInterface;
@ -35,6 +35,9 @@ class PageBuilder
*/ */
protected $session; protected $session;
/** @var LoggerInterface */
protected $logger;
/** /**
* @var BookmarkServiceInterface $bookmarkService instance. * @var BookmarkServiceInterface $bookmarkService instance.
*/ */
@ -54,17 +57,25 @@ class PageBuilder
* PageBuilder constructor. * PageBuilder constructor.
* $tpl is initialized at false for lazy loading. * $tpl is initialized at false for lazy loading.
* *
* @param ConfigManager $conf Configuration Manager instance (reference). * @param ConfigManager $conf Configuration Manager instance (reference).
* @param array $session $_SESSION array * @param array $session $_SESSION array
* @param BookmarkServiceInterface $linkDB instance. * @param LoggerInterface $logger
* @param string $token Session token * @param null $linkDB instance.
* @param bool $isLoggedIn * @param null $token Session token
* @param bool $isLoggedIn
*/ */
public function __construct(&$conf, $session, $linkDB = null, $token = null, $isLoggedIn = false) public function __construct(
{ ConfigManager &$conf,
array $session,
LoggerInterface $logger,
$linkDB = null,
$token = null,
$isLoggedIn = false
) {
$this->tpl = false; $this->tpl = false;
$this->conf = $conf; $this->conf = $conf;
$this->session = $session; $this->session = $session;
$this->logger = $logger;
$this->bookmarkService = $linkDB; $this->bookmarkService = $linkDB;
$this->token = $token; $this->token = $token;
$this->isLoggedIn = $isLoggedIn; $this->isLoggedIn = $isLoggedIn;
@ -98,7 +109,7 @@ private function initialize()
$this->tpl->assign('newVersion', escape($version)); $this->tpl->assign('newVersion', escape($version));
$this->tpl->assign('versionError', ''); $this->tpl->assign('versionError', '');
} catch (Exception $exc) { } catch (Exception $exc) {
logm($this->conf->get('resource.log'), $_SERVER['REMOTE_ADDR'], $exc->getMessage()); $this->logger->error(format_log('Error: ' . $exc->getMessage(), client_ip_id($_SERVER)));
$this->tpl->assign('newVersion', ''); $this->tpl->assign('newVersion', '');
$this->tpl->assign('versionError', escape($exc->getMessage())); $this->tpl->assign('versionError', escape($exc->getMessage()));
} }

View file

@ -3,6 +3,7 @@
namespace Shaarli\Security; namespace Shaarli\Security;
use Psr\Log\LoggerInterface;
use Shaarli\FileUtils; use Shaarli\FileUtils;
/** /**
@ -28,8 +29,8 @@ class BanManager
/** @var string Path to the file containing IP bans and failures */ /** @var string Path to the file containing IP bans and failures */
protected $banFile; protected $banFile;
/** @var string Path to the log file, used to log bans */ /** @var LoggerInterface Path to the log file, used to log bans */
protected $logFile; protected $logger;
/** @var array List of IP with their associated number of failed attempts */ /** @var array List of IP with their associated number of failed attempts */
protected $failures = []; protected $failures = [];
@ -40,18 +41,19 @@ class BanManager
/** /**
* BanManager constructor. * BanManager constructor.
* *
* @param array $trustedProxies List of allowed proxies IP * @param array $trustedProxies List of allowed proxies IP
* @param int $nbAttempts Number of allowed failed attempt before the ban * @param int $nbAttempts Number of allowed failed attempt before the ban
* @param int $banDuration Ban duration in seconds * @param int $banDuration Ban duration in seconds
* @param string $banFile Path to the file containing IP bans and failures * @param string $banFile Path to the file containing IP bans and failures
* @param string $logFile Path to the log file, used to log bans * @param LoggerInterface $logger PSR-3 logger to save login attempts in log directory
*/ */
public function __construct($trustedProxies, $nbAttempts, $banDuration, $banFile, $logFile) { public function __construct($trustedProxies, $nbAttempts, $banDuration, $banFile, LoggerInterface $logger) {
$this->trustedProxies = $trustedProxies; $this->trustedProxies = $trustedProxies;
$this->nbAttempts = $nbAttempts; $this->nbAttempts = $nbAttempts;
$this->banDuration = $banDuration; $this->banDuration = $banDuration;
$this->banFile = $banFile; $this->banFile = $banFile;
$this->logFile = $logFile; $this->logger = $logger;
$this->readBanFile(); $this->readBanFile();
} }
@ -78,11 +80,7 @@ public function handleFailedAttempt($server)
if ($this->failures[$ip] >= $this->nbAttempts) { if ($this->failures[$ip] >= $this->nbAttempts) {
$this->bans[$ip] = time() + $this->banDuration; $this->bans[$ip] = time() + $this->banDuration;
logm( $this->logger->info(format_log('IP address banned from login: '. $ip, $ip));
$this->logFile,
$server['REMOTE_ADDR'],
'IP address banned from login: '. $ip
);
} }
$this->writeBanFile(); $this->writeBanFile();
} }
@ -138,7 +136,7 @@ public function isBanned($server)
unset($this->failures[$ip]); unset($this->failures[$ip]);
} }
unset($this->bans[$ip]); unset($this->bans[$ip]);
logm($this->logFile, $server['REMOTE_ADDR'], 'Ban lifted for: '. $ip); $this->logger->info(format_log('Ban lifted for: '. $ip, $ip));
$this->writeBanFile(); $this->writeBanFile();
return false; return false;

View file

@ -2,6 +2,7 @@
namespace Shaarli\Security; namespace Shaarli\Security;
use Exception; use Exception;
use Psr\Log\LoggerInterface;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
/** /**
@ -31,26 +32,30 @@ class LoginManager
protected $staySignedInToken = ''; protected $staySignedInToken = '';
/** @var CookieManager */ /** @var CookieManager */
protected $cookieManager; protected $cookieManager;
/** @var LoggerInterface */
protected $logger;
/** /**
* Constructor * Constructor
* *
* @param ConfigManager $configManager Configuration Manager instance * @param ConfigManager $configManager Configuration Manager instance
* @param SessionManager $sessionManager SessionManager instance * @param SessionManager $sessionManager SessionManager instance
* @param CookieManager $cookieManager CookieManager instance * @param CookieManager $cookieManager CookieManager instance
* @param BanManager $banManager
* @param LoggerInterface $logger Used to log login attempts
*/ */
public function __construct($configManager, $sessionManager, $cookieManager) public function __construct(
{ ConfigManager $configManager,
SessionManager $sessionManager,
CookieManager $cookieManager,
BanManager $banManager,
LoggerInterface $logger
) {
$this->configManager = $configManager; $this->configManager = $configManager;
$this->sessionManager = $sessionManager; $this->sessionManager = $sessionManager;
$this->cookieManager = $cookieManager; $this->cookieManager = $cookieManager;
$this->banManager = new BanManager( $this->banManager = $banManager;
$this->configManager->get('security.trusted_proxies', []), $this->logger = $logger;
$this->configManager->get('security.ban_after'),
$this->configManager->get('security.ban_duration'),
$this->configManager->get('resource.ban_file', 'data/ipbans.php'),
$this->configManager->get('resource.log')
);
if ($this->configManager->get('security.open_shaarli') === true) { if ($this->configManager->get('security.open_shaarli') === true) {
$this->openShaarli = true; $this->openShaarli = true;
@ -129,48 +134,34 @@ public function isLoggedIn(): bool
/** /**
* Check user credentials are valid * Check user credentials are valid
* *
* @param string $remoteIp Remote client IP address
* @param string $clientIpId Client IP address identifier * @param string $clientIpId Client IP address identifier
* @param string $login Username * @param string $login Username
* @param string $password Password * @param string $password Password
* *
* @return bool true if the provided credentials are valid, false otherwise * @return bool true if the provided credentials are valid, false otherwise
*/ */
public function checkCredentials($remoteIp, $clientIpId, $login, $password) public function checkCredentials($clientIpId, $login, $password)
{ {
// Check login matches config
if ($login !== $this->configManager->get('credentials.login')) {
return false;
}
// Check credentials // Check credentials
try { try {
$useLdapLogin = !empty($this->configManager->get('ldap.host')); $useLdapLogin = !empty($this->configManager->get('ldap.host'));
if ((false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password)) if ($login === $this->configManager->get('credentials.login')
|| (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password)) && (
(false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password))
|| (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password))
)
) { ) {
$this->sessionManager->storeLoginInfo($clientIpId); $this->sessionManager->storeLoginInfo($clientIpId);
logm( $this->logger->info(format_log('Login successful', $clientIpId));
$this->configManager->get('resource.log'),
$remoteIp, return true;
'Login successful'
);
return true;
} }
} } catch(Exception $exception) {
catch(Exception $exception) { $this->logger->info(format_log('Exception while checking credentials: ' . $exception, $clientIpId));
logm(
$this->configManager->get('resource.log'),
$remoteIp,
'Exception while checking credentials: ' . $exception
);
} }
logm( $this->logger->info(format_log('Login failed for user ' . $login, $clientIpId));
$this->configManager->get('resource.log'),
$remoteIp,
'Login failed for user ' . $login
);
return false; return false;
} }

View file

@ -23,6 +23,7 @@
"erusev/parsedown": "^1.6", "erusev/parsedown": "^1.6",
"erusev/parsedown-extra": "^0.8.1", "erusev/parsedown-extra": "^0.8.1",
"gettext/gettext": "^4.4", "gettext/gettext": "^4.4",
"katzgrau/klogger": "^1.2",
"malkusch/lock": "^2.1", "malkusch/lock": "^2.1",
"pubsubhubbub/publisher": "dev-master", "pubsubhubbub/publisher": "dev-master",
"shaarli/netscape-bookmark-parser": "^2.1", "shaarli/netscape-bookmark-parser": "^2.1",

2
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "932b191006135ff8be495aa0b4ba7e09", "content-hash": "61360efbb2e1ba4c4fe00ce1f7a78ec5",
"packages": [ "packages": [
{ {
"name": "arthurhoaro/web-thumbnailer", "name": "arthurhoaro/web-thumbnailer",

View file

@ -25,9 +25,12 @@
require_once __DIR__ . '/init.php'; require_once __DIR__ . '/init.php';
use Katzgrau\KLogger\Logger;
use Psr\Log\LogLevel;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Container\ContainerBuilder; use Shaarli\Container\ContainerBuilder;
use Shaarli\Languages; use Shaarli\Languages;
use Shaarli\Security\BanManager;
use Shaarli\Security\CookieManager; use Shaarli\Security\CookieManager;
use Shaarli\Security\LoginManager; use Shaarli\Security\LoginManager;
use Shaarli\Security\SessionManager; use Shaarli\Security\SessionManager;
@ -48,10 +51,22 @@
}); });
} }
$logger = new Logger(
dirname($conf->get('resource.log')),
!$conf->get('dev.debug') ? LogLevel::INFO : LogLevel::DEBUG,
['filename' => basename($conf->get('resource.log'))]
);
$sessionManager = new SessionManager($_SESSION, $conf, session_save_path()); $sessionManager = new SessionManager($_SESSION, $conf, session_save_path());
$sessionManager->initialize(); $sessionManager->initialize();
$cookieManager = new CookieManager($_COOKIE); $cookieManager = new CookieManager($_COOKIE);
$loginManager = new LoginManager($conf, $sessionManager, $cookieManager); $banManager = new BanManager(
$conf->get('security.trusted_proxies', []),
$conf->get('security.ban_after'),
$conf->get('security.ban_duration'),
$conf->get('resource.ban_file', 'data/ipbans.php'),
$logger
);
$loginManager = new LoginManager($conf, $sessionManager, $cookieManager, $banManager, $logger);
$loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
// Sniff browser language and set date format accordingly. // Sniff browser language and set date format accordingly.
@ -71,7 +86,7 @@
$loginManager->checkLoginState(client_ip_id($_SERVER)); $loginManager->checkLoginState(client_ip_id($_SERVER));
$containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager); $containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager, $logger);
$container = $containerBuilder->build(); $container = $containerBuilder->build();
$app = new App($container); $app = new App($container);

View file

@ -63,41 +63,25 @@ protected function getLastLogEntry()
} }
/** /**
* Log a message to a file - IPv4 client address * Format a log a message - IPv4 client address
*/ */
public function testLogmIp4() public function testFormatLogIp4()
{ {
$logMessage = 'IPv4 client connected'; $message = 'IPv4 client connected';
logm(self::$testLogFile, '127.0.0.1', $logMessage); $log = format_log($message, '127.0.0.1');
list($date, $ip, $message) = $this->getLastLogEntry();
$this->assertInstanceOf( static::assertSame('- 127.0.0.1 - IPv4 client connected', $log);
'DateTime',
DateTime::createFromFormat(self::$dateFormat, $date)
);
$this->assertTrue(
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false
);
$this->assertEquals($logMessage, $message);
} }
/** /**
* Log a message to a file - IPv6 client address * Format a log a message - IPv6 client address
*/ */
public function testLogmIp6() public function testFormatLogIp6()
{ {
$logMessage = 'IPv6 client connected'; $message = 'IPv6 client connected';
logm(self::$testLogFile, '2001:db8::ff00:42:8329', $logMessage); $log = format_log($message, '2001:db8::ff00:42:8329');
list($date, $ip, $message) = $this->getLastLogEntry();
$this->assertInstanceOf( static::assertSame('- 2001:db8::ff00:42:8329 - IPv6 client connected', $log);
'DateTime',
DateTime::createFromFormat(self::$dateFormat, $date)
);
$this->assertTrue(
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false
);
$this->assertEquals($logMessage, $message);
} }
/** /**

View file

@ -4,6 +4,7 @@
namespace Shaarli\Container; namespace Shaarli\Container;
use Psr\Log\LoggerInterface;
use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Feed\FeedBuilder; use Shaarli\Feed\FeedBuilder;
@ -55,7 +56,8 @@ public function setUp(): void
$this->conf, $this->conf,
$this->sessionManager, $this->sessionManager,
$this->cookieManager, $this->cookieManager,
$this->loginManager $this->loginManager,
$this->createMock(LoggerInterface::class)
); );
} }
@ -73,6 +75,7 @@ public function testBuildContainer(): void
static::assertInstanceOf(History::class, $container->history); static::assertInstanceOf(History::class, $container->history);
static::assertInstanceOf(HttpAccess::class, $container->httpAccess); static::assertInstanceOf(HttpAccess::class, $container->httpAccess);
static::assertInstanceOf(LoginManager::class, $container->loginManager); static::assertInstanceOf(LoginManager::class, $container->loginManager);
static::assertInstanceOf(LoggerInterface::class, $container->logger);
static::assertInstanceOf(MetadataRetriever::class, $container->metadataRetriever); static::assertInstanceOf(MetadataRetriever::class, $container->metadataRetriever);
static::assertInstanceOf(NetscapeBookmarkUtils::class, $container->netscapeBookmarkUtils); static::assertInstanceOf(NetscapeBookmarkUtils::class, $container->netscapeBookmarkUtils);
static::assertInstanceOf(PageBuilder::class, $container->pageBuilder); static::assertInstanceOf(PageBuilder::class, $container->pageBuilder);

View file

@ -195,7 +195,7 @@ public function testProcessLoginWithValidParameters(): void
$this->container->loginManager $this->container->loginManager
->expects(static::once()) ->expects(static::once())
->method('checkCredentials') ->method('checkCredentials')
->with('1.2.3.4', '1.2.3.4', 'bob', 'pass') ->with('1.2.3.4', 'bob', 'pass')
->willReturn(true) ->willReturn(true)
; ;
$this->container->loginManager->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8))); $this->container->loginManager->method('getStaySignedInToken')->willReturn(bin2hex(random_bytes(8)));

View file

@ -3,6 +3,7 @@
namespace Shaarli\Security; namespace Shaarli\Security;
use Psr\Log\LoggerInterface;
use Shaarli\FileUtils; use Shaarli\FileUtils;
use Shaarli\TestCase; use Shaarli\TestCase;
@ -387,7 +388,7 @@ protected function getNewBanManagerInstance()
3, 3,
1800, 1800,
$this->banFile, $this->banFile,
$this->logFile $this->createMock(LoggerInterface::class)
); );
} }
} }

View file

@ -2,6 +2,8 @@
namespace Shaarli\Security; namespace Shaarli\Security;
use Psr\Log\LoggerInterface;
use Shaarli\FakeConfigManager;
use Shaarli\TestCase; use Shaarli\TestCase;
/** /**
@ -9,7 +11,7 @@
*/ */
class LoginManagerTest extends TestCase class LoginManagerTest extends TestCase
{ {
/** @var \FakeConfigManager Configuration Manager instance */ /** @var FakeConfigManager Configuration Manager instance */
protected $configManager = null; protected $configManager = null;
/** @var LoginManager Login Manager instance */ /** @var LoginManager Login Manager instance */
@ -60,6 +62,9 @@ class LoginManagerTest extends TestCase
/** @var CookieManager */ /** @var CookieManager */
protected $cookieManager; protected $cookieManager;
/** @var BanManager */
protected $banManager;
/** /**
* Prepare or reset test resources * Prepare or reset test resources
*/ */
@ -71,7 +76,7 @@ protected function setUp(): void
$this->passwordHash = sha1($this->password . $this->login . $this->salt); $this->passwordHash = sha1($this->password . $this->login . $this->salt);
$this->configManager = new \FakeConfigManager([ $this->configManager = new FakeConfigManager([
'credentials.login' => $this->login, 'credentials.login' => $this->login,
'credentials.hash' => $this->passwordHash, 'credentials.hash' => $this->passwordHash,
'credentials.salt' => $this->salt, 'credentials.salt' => $this->salt,
@ -91,18 +96,29 @@ protected function setUp(): void
return $this->cookie[$key] ?? null; return $this->cookie[$key] ?? null;
}); });
$this->sessionManager = new SessionManager($this->session, $this->configManager, 'session_path'); $this->sessionManager = new SessionManager($this->session, $this->configManager, 'session_path');
$this->loginManager = new LoginManager($this->configManager, $this->sessionManager, $this->cookieManager); $this->banManager = $this->createMock(BanManager::class);
$this->loginManager = new LoginManager(
$this->configManager,
$this->sessionManager,
$this->cookieManager,
$this->banManager,
$this->createMock(LoggerInterface::class)
);
$this->server['REMOTE_ADDR'] = $this->ipAddr; $this->server['REMOTE_ADDR'] = $this->ipAddr;
} }
/** /**
* Record a failed login attempt * Record a failed login attempt
*/ */
public function testHandleFailedLogin() public function testHandleFailedLogin(): void
{ {
$this->banManager->expects(static::exactly(2))->method('handleFailedAttempt');
$this->banManager->method('isBanned')->willReturn(true);
$this->loginManager->handleFailedLogin($this->server); $this->loginManager->handleFailedLogin($this->server);
$this->loginManager->handleFailedLogin($this->server); $this->loginManager->handleFailedLogin($this->server);
$this->assertFalse($this->loginManager->canLogin($this->server));
static::assertFalse($this->loginManager->canLogin($this->server));
} }
/** /**
@ -114,8 +130,13 @@ public function testHandleFailedLoginBehindTrustedProxy()
'REMOTE_ADDR' => $this->trustedProxy, 'REMOTE_ADDR' => $this->trustedProxy,
'HTTP_X_FORWARDED_FOR' => $this->ipAddr, 'HTTP_X_FORWARDED_FOR' => $this->ipAddr,
]; ];
$this->banManager->expects(static::exactly(2))->method('handleFailedAttempt');
$this->banManager->method('isBanned')->willReturn(true);
$this->loginManager->handleFailedLogin($server); $this->loginManager->handleFailedLogin($server);
$this->loginManager->handleFailedLogin($server); $this->loginManager->handleFailedLogin($server);
$this->assertFalse($this->loginManager->canLogin($server)); $this->assertFalse($this->loginManager->canLogin($server));
} }
@ -196,10 +217,16 @@ public function testGenerateStaySignedInTokenSessionProtectionDisabled()
*/ */
public function testCheckLoginStateNotConfigured() public function testCheckLoginStateNotConfigured()
{ {
$configManager = new \FakeConfigManager([ $configManager = new FakeConfigManager([
'resource.ban_file' => $this->banFile, 'resource.ban_file' => $this->banFile,
]); ]);
$loginManager = new LoginManager($configManager, null, $this->cookieManager); $loginManager = new LoginManager(
$configManager,
$this->sessionManager,
$this->cookieManager,
$this->banManager,
$this->createMock(LoggerInterface::class)
);
$loginManager->checkLoginState(''); $loginManager->checkLoginState('');
$this->assertFalse($loginManager->isLoggedIn()); $this->assertFalse($loginManager->isLoggedIn());
@ -270,7 +297,7 @@ public function testCheckLoginStateClientIpChanged()
public function testCheckCredentialsWrongLogin() public function testCheckCredentialsWrongLogin()
{ {
$this->assertFalse( $this->assertFalse(
$this->loginManager->checkCredentials('', '', 'b4dl0g1n', $this->password) $this->loginManager->checkCredentials('', 'b4dl0g1n', $this->password)
); );
} }
@ -280,7 +307,7 @@ public function testCheckCredentialsWrongLogin()
public function testCheckCredentialsWrongPassword() public function testCheckCredentialsWrongPassword()
{ {
$this->assertFalse( $this->assertFalse(
$this->loginManager->checkCredentials('', '', $this->login, 'b4dp455wd') $this->loginManager->checkCredentials('', $this->login, 'b4dp455wd')
); );
} }
@ -290,7 +317,7 @@ public function testCheckCredentialsWrongPassword()
public function testCheckCredentialsWrongLoginAndPassword() public function testCheckCredentialsWrongLoginAndPassword()
{ {
$this->assertFalse( $this->assertFalse(
$this->loginManager->checkCredentials('', '', 'b4dl0g1n', 'b4dp455wd') $this->loginManager->checkCredentials('', 'b4dl0g1n', 'b4dp455wd')
); );
} }
@ -300,7 +327,7 @@ public function testCheckCredentialsWrongLoginAndPassword()
public function testCheckCredentialsGoodLoginAndPassword() public function testCheckCredentialsGoodLoginAndPassword()
{ {
$this->assertTrue( $this->assertTrue(
$this->loginManager->checkCredentials('', '', $this->login, $this->password) $this->loginManager->checkCredentials('', $this->login, $this->password)
); );
} }
@ -311,7 +338,7 @@ public function testCheckCredentialsFromUnreachableLdap()
{ {
$this->configManager->set('ldap.host', 'dummy'); $this->configManager->set('ldap.host', 'dummy');
$this->assertFalse( $this->assertFalse(
$this->loginManager->checkCredentials('', '', $this->login, $this->password) $this->loginManager->checkCredentials('', $this->login, $this->password)
); );
} }

View file

@ -2,6 +2,7 @@
namespace Shaarli\Security; namespace Shaarli\Security;
use Shaarli\FakeConfigManager;
use Shaarli\TestCase; use Shaarli\TestCase;
/** /**
@ -12,7 +13,7 @@ class SessionManagerTest extends TestCase
/** @var array Session ID hashes */ /** @var array Session ID hashes */
protected static $sidHashes = null; protected static $sidHashes = null;
/** @var \FakeConfigManager ConfigManager substitute for testing */ /** @var FakeConfigManager ConfigManager substitute for testing */
protected $conf = null; protected $conf = null;
/** @var array $_SESSION array for testing */ /** @var array $_SESSION array for testing */
@ -34,7 +35,7 @@ public static function setUpBeforeClass(): void
*/ */
protected function setUp(): void protected function setUp(): void
{ {
$this->conf = new \FakeConfigManager([ $this->conf = new FakeConfigManager([
'credentials.login' => 'johndoe', 'credentials.login' => 'johndoe',
'credentials.salt' => 'salt', 'credentials.salt' => 'salt',
'security.session_protection_disabled' => false, 'security.session_protection_disabled' => false,

View file

@ -1,9 +1,13 @@
<?php <?php
namespace Shaarli;
use Shaarli\Config\ConfigManager;
/** /**
* Fake ConfigManager * Fake ConfigManager
*/ */
class FakeConfigManager class FakeConfigManager extends ConfigManager
{ {
protected $values = []; protected $values = [];
@ -23,7 +27,7 @@ public function __construct($values = [])
* @param string $key Key of the value to set * @param string $key Key of the value to set
* @param mixed $value Value to set * @param mixed $value Value to set
*/ */
public function set($key, $value) public function set($key, $value, $write = false, $isLoggedIn = false)
{ {
$this->values[$key] = $value; $this->values[$key] = $value;
} }
@ -35,7 +39,7 @@ public function set($key, $value)
* *
* @return mixed The value if set, else the name of the key * @return mixed The value if set, else the name of the key
*/ */
public function get($key) public function get($key, $default = '')
{ {
if (isset($this->values[$key])) { if (isset($this->values[$key])) {
return $this->values[$key]; return $this->values[$key];