Process password change controller through Slim

This commit is contained in:
ArthurHoaro 2020-05-27 13:35:48 +02:00
parent ba43064ddb
commit ef00f9d203
24 changed files with 450 additions and 182 deletions

View file

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Admin;
use Shaarli\Container\ShaarliContainer;
use Shaarli\Front\Exception\OpenShaarliPasswordException;
use Shaarli\Front\Exception\ShaarliFrontException;
use Slim\Http\Request;
use Slim\Http\Response;
use Throwable;
/**
* Class PasswordController
*
* Slim controller used to handle passwords update.
*/
class PasswordController extends ShaarliAdminController
{
public function __construct(ShaarliContainer $container)
{
parent::__construct($container);
$this->assignView(
'pagetitle',
t('Change password') .' - '. $this->container->conf->get('general.title', 'Shaarli')
);
}
/**
* GET /password - Displays the change password template
*/
public function index(Request $request, Response $response): Response
{
return $response->write($this->render('changepassword'));
}
/**
* POST /password - Change admin password - existing and new passwords need to be provided.
*/
public function change(Request $request, Response $response): Response
{
$this->checkToken($request);
if ($this->container->conf->get('security.open_shaarli', false)) {
throw new OpenShaarliPasswordException();
}
$oldPassword = $request->getParam('oldpassword');
$newPassword = $request->getParam('setpassword');
if (empty($newPassword) || empty($oldPassword)) {
$this->saveErrorMessage(t('You must provide the current and new password to change it.'));
return $response
->withStatus(400)
->write($this->render('changepassword'))
;
}
// Make sure old password is correct.
$oldHash = sha1(
$oldPassword .
$this->container->conf->get('credentials.login') .
$this->container->conf->get('credentials.salt')
);
if ($oldHash !== $this->container->conf->get('credentials.hash')) {
$this->saveErrorMessage(t('The old password is not correct.'));
return $response
->withStatus(400)
->write($this->render('changepassword'))
;
}
// Save new password
// Salt renders rainbow-tables attacks useless.
$this->container->conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
$this->container->conf->set(
'credentials.hash',
sha1(
$newPassword
. $this->container->conf->get('credentials.login')
. $this->container->conf->get('credentials.salt')
)
);
try {
$this->container->conf->write($this->container->loginManager->isLoggedIn());
} catch (Throwable $e) {
throw new ShaarliFrontException($e->getMessage(), 500, $e);
}
$this->saveSuccessMessage(t('Your password has been changed'));
return $response->write($this->render('changepassword'));
}
}

View file

@ -7,7 +7,19 @@
use Shaarli\Container\ShaarliContainer; use Shaarli\Container\ShaarliContainer;
use Shaarli\Front\Controller\Visitor\ShaarliVisitorController; use Shaarli\Front\Controller\Visitor\ShaarliVisitorController;
use Shaarli\Front\Exception\UnauthorizedException; use Shaarli\Front\Exception\UnauthorizedException;
use Shaarli\Front\Exception\WrongTokenException;
use Shaarli\Security\SessionManager;
use Slim\Http\Request;
/**
* Class ShaarliAdminController
*
* All admin controllers (for logged in users) MUST extend this abstract class.
* It makes sure that the user is properly logged in, and otherwise throw an exception
* which will redirect to the login page.
*
* @package Shaarli\Front\Controller\Admin
*/
abstract class ShaarliAdminController extends ShaarliVisitorController abstract class ShaarliAdminController extends ShaarliVisitorController
{ {
public function __construct(ShaarliContainer $container) public function __construct(ShaarliContainer $container)
@ -18,4 +30,51 @@ public function __construct(ShaarliContainer $container)
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
} }
/**
* Any persistent action to the config or data store must check the XSRF token validity.
*/
protected function checkToken(Request $request): void
{
if (!$this->container->sessionManager->checkToken($request->getParam('token'))) {
throw new WrongTokenException();
}
}
/**
* Save a SUCCESS message in user session, which will be displayed on any template page.
*/
protected function saveSuccessMessage(string $message): void
{
$this->saveMessage(SessionManager::KEY_SUCCESS_MESSAGES, $message);
}
/**
* Save a WARNING message in user session, which will be displayed on any template page.
*/
protected function saveWarningMessage(string $message): void
{
$this->saveMessage(SessionManager::KEY_WARNING_MESSAGES, $message);
}
/**
* Save an ERROR message in user session, which will be displayed on any template page.
*/
protected function saveErrorMessage(string $message): void
{
$this->saveMessage(SessionManager::KEY_ERROR_MESSAGES, $message);
}
/**
* Use the sessionManager to save the provided message using the proper type.
*
* @param string $type successed/warnings/errors
*/
protected function saveMessage(string $type, string $message): void
{
$messages = $this->container->sessionManager->getSessionParameter($type) ?? [];
$messages[] = $message;
$this->container->sessionManager->setSessionParameter($type, $messages);
}
} }

View file

@ -9,6 +9,14 @@
use Slim\Http\Request; use Slim\Http\Request;
use Slim\Http\Response; use Slim\Http\Response;
/**
* Class ShaarliVisitorController
*
* All controllers accessible by visitors (non logged in users) should extend this abstract class.
* Contains a few helper function for template rendering, plugins, etc.
*
* @package Shaarli\Front\Controller\Visitor
*/
abstract class ShaarliVisitorController abstract class ShaarliVisitorController
{ {
/** @var ShaarliContainer */ /** @var ShaarliContainer */

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Exception;
/**
* Class OpenShaarliPasswordException
*
* Raised if the user tries to change the admin password on an open shaarli instance.
*/
class OpenShaarliPasswordException extends ShaarliFrontException
{
public function __construct()
{
parent::__construct(t('You are not supposed to change a password on an Open Shaarli.'), 403);
}
}

View file

@ -9,11 +9,11 @@
/** /**
* Class ShaarliException * Class ShaarliException
* *
* Abstract exception class used to defined any custom exception thrown during front rendering. * Exception class used to defined any custom exception thrown during front rendering.
* *
* @package Front\Exception * @package Front\Exception
*/ */
abstract class ShaarliFrontException extends \Exception class ShaarliFrontException extends \Exception
{ {
/** Override parent constructor to force $message and $httpCode parameters to be set. */ /** Override parent constructor to force $message and $httpCode parameters to be set. */
public function __construct(string $message, int $httpCode, Throwable $previous = null) public function __construct(string $message, int $httpCode, Throwable $previous = null)

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Exception;
/**
* Class OpenShaarliPasswordException
*
* Raised if the user tries to perform an action with an invalid XSRF token.
*/
class WrongTokenException extends ShaarliFrontException
{
public function __construct()
{
parent::__construct(t('Wrong token.'), 403);
}
}

View file

@ -7,6 +7,7 @@
use Shaarli\ApplicationUtils; use Shaarli\ApplicationUtils;
use Shaarli\Bookmark\BookmarkServiceInterface; use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager; use Shaarli\Config\ConfigManager;
use Shaarli\Security\SessionManager;
use Shaarli\Thumbnailer; use Shaarli\Thumbnailer;
/** /**
@ -136,17 +137,28 @@ private function initialize()
$this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width')); $this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width'));
$this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height')); $this->tpl->assign('thumbnails_height', $this->conf->get('thumbnails.height'));
if (!empty($_SESSION['warnings'])) {
$this->tpl->assign('global_warnings', $_SESSION['warnings']);
unset($_SESSION['warnings']);
}
$this->tpl->assign('formatter', $this->conf->get('formatter', 'default')); $this->tpl->assign('formatter', $this->conf->get('formatter', 'default'));
// To be removed with a proper theme configuration. // To be removed with a proper theme configuration.
$this->tpl->assign('conf', $this->conf); $this->tpl->assign('conf', $this->conf);
} }
protected function finalize(): void
{
// TODO: use the SessionManager
$messageKeys = [
SessionManager::KEY_SUCCESS_MESSAGES,
SessionManager::KEY_WARNING_MESSAGES,
SessionManager::KEY_ERROR_MESSAGES
];
foreach ($messageKeys as $messageKey) {
if (!empty($_SESSION[$messageKey])) {
$this->tpl->assign('global_' . $messageKey, $_SESSION[$messageKey]);
unset($_SESSION[$messageKey]);
}
}
}
/** /**
* The following assign() method is basically the same as RainTPL (except lazy loading) * The following assign() method is basically the same as RainTPL (except lazy loading)
* *
@ -196,6 +208,8 @@ public function renderPage($page)
$this->initialize(); $this->initialize();
} }
$this->finalize();
$this->tpl->draw($page); $this->tpl->draw($page);
} }
@ -213,6 +227,8 @@ public function render(string $page): string
$this->initialize(); $this->initialize();
} }
$this->finalize();
return $this->tpl->draw($page, true); return $this->tpl->draw($page, true);
} }

View file

@ -12,6 +12,10 @@ class SessionManager
public const KEY_VISIBILITY = 'visibility'; public const KEY_VISIBILITY = 'visibility';
public const KEY_UNTAGGED_ONLY = 'untaggedonly'; public const KEY_UNTAGGED_ONLY = 'untaggedonly';
public const KEY_SUCCESS_MESSAGES = 'successes';
public const KEY_WARNING_MESSAGES = 'warnings';
public const KEY_ERROR_MESSAGES = 'errors';
/** @var int Session expiration timeout, in seconds */ /** @var int Session expiration timeout, in seconds */
public static $SHORT_TIMEOUT = 3600; // 1 hour public static $SHORT_TIMEOUT = 3600; // 1 hour

View file

@ -507,57 +507,9 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
// -------- User wants to change his/her password. // -------- User wants to change his/her password.
if ($targetPage == Router::$PAGE_CHANGEPASSWORD) { if ($targetPage == Router::$PAGE_CHANGEPASSWORD) {
if ($conf->get('security.open_shaarli')) { header('Location: ./password');
die(t('You are not supposed to change a password on an Open Shaarli.'));
}
if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) {
if (!$sessionManager->checkToken($_POST['token'])) {
die(t('Wrong token.')); // Go away!
}
// Make sure old password is correct.
$oldhash = sha1(
$_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')
);
if ($oldhash != $conf->get('credentials.hash')) {
echo '<script>alert("'
. t('The old password is not correct.')
.'");document.location=\'./?do=changepasswd\';</script>';
exit; exit;
} }
// Save new password
// Salt renders rainbow-tables attacks useless.
$conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
$conf->set(
'credentials.hash',
sha1(
$_POST['setpassword']
. $conf->get('credentials.login')
. $conf->get('credentials.salt')
)
);
try {
$conf->write($loginManager->isLoggedIn());
} catch (Exception $e) {
error_log(
'ERROR while writing config file after changing password.' . PHP_EOL .
$e->getMessage()
);
// TODO: do not handle exceptions/errors in JS.
echo '<script>alert("'. $e->getMessage() .'");document.location=\'./tools\';</script>';
exit;
}
echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'./tools\';</script>';
exit;
} else {
// show the change password form.
$PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli'));
$PAGE->renderPage('changepassword');
exit;
}
}
// -------- User wants to change configuration // -------- User wants to change configuration
if ($targetPage == Router::$PAGE_CONFIGURE) { if ($targetPage == Router::$PAGE_CONFIGURE) {
@ -1504,6 +1456,8 @@ function install($conf, $sessionManager, $loginManager)
/* -- LOGGED IN -- */ /* -- LOGGED IN -- */
$this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index')->setName('logout'); $this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index')->setName('logout');
$this->get('/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index')->setName('tools'); $this->get('/tools', '\Shaarli\Front\Controller\Admin\ToolsController:index')->setName('tools');
$this->get('/password', '\Shaarli\Front\Controller\Admin\PasswordController:index')->setName('password');
$this->post('/password', '\Shaarli\Front\Controller\Admin\PasswordController:change')->setName('changePassword');
$this $this
->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage') ->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage')

View file

@ -6,7 +6,6 @@
use Shaarli\Container\ShaarliTestContainer; use Shaarli\Container\ShaarliTestContainer;
use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper; use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper;
use Shaarli\Security\LoginManager;
/** /**
* Trait FrontControllerMockHelper * Trait FrontControllerMockHelper
@ -28,7 +27,7 @@ protected function createContainer(): void
{ {
$this->parentCreateContainer(); $this->parentCreateContainer();
$this->container->loginManager = $this->createMock(LoginManager::class);
$this->container->loginManager->method('isLoggedIn')->willReturn(true); $this->container->loginManager->method('isLoggedIn')->willReturn(true);
$this->container->sessionManager->method('checkToken')->willReturn(true);
} }
} }

View file

@ -35,8 +35,6 @@ public function setUp(): void
public function testValidControllerInvoke(): void public function testValidControllerInvoke(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -0,0 +1,186 @@
<?php
declare(strict_types=1);
namespace Shaarli\Front\Controller\Admin;
use PHPUnit\Framework\TestCase;
use Shaarli\Config\ConfigManager;
use Shaarli\Front\Exception\WrongTokenException;
use Shaarli\Security\SessionManager;
use Slim\Http\Request;
use Slim\Http\Response;
class PasswordControllerTest extends TestCase
{
use FrontAdminControllerMockHelper;
/** @var PasswordController */
protected $controller;
/** @var mixed[] Variables assigned to the template */
protected $assignedVariables = [];
public function setUp(): void
{
$this->createContainer();
$this->assignTemplateVars($this->assignedVariables);
$this->controller = new PasswordController($this->container);
}
/**
* Test displaying the change password page.
*/
public function testGetPage(): void
{
$request = $this->createMock(Request::class);
$response = new Response();
$result = $this->controller->index($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('changepassword', (string) $result->getBody());
static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']);
}
/**
* Change the password with valid parameters
*/
public function testPostNewPasswordDefault(): void
{
$request = $this->createMock(Request::class);
$request->method('getParam')->willReturnCallback(function (string $key): string {
if ('oldpassword' === $key) {
return 'old';
}
if ('setpassword' === $key) {
return 'new';
}
return $key;
});
$response = new Response();
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->conf->method('get')->willReturnCallback(function (string $key, $default) {
if ('credentials.hash' === $key) {
return sha1('old' . 'credentials.login' . 'credentials.salt');
}
return strpos($key, 'credentials') !== false ? $key : $default;
});
$this->container->conf->expects(static::once())->method('write')->with(true);
$this->container->conf
->method('set')
->willReturnCallback(function (string $key, string $value) {
if ('credentials.hash' === $key) {
static::assertSame(sha1('new' . 'credentials.login' . 'credentials.salt'), $value);
}
})
;
$result = $this->controller->change($request, $response);
static::assertSame(200, $result->getStatusCode());
static::assertSame('changepassword', (string) $result->getBody());
static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']);
}
/**
* Change the password with a wrong existing password
*/
public function testPostNewPasswordWrongOldPassword(): void
{
$request = $this->createMock(Request::class);
$request->method('getParam')->willReturnCallback(function (string $key): string {
if ('oldpassword' === $key) {
return 'wrong';
}
if ('setpassword' === $key) {
return 'new';
}
return $key;
});
$response = new Response();
$this->container->conf = $this->createMock(ConfigManager::class);
$this->container->conf->method('get')->willReturnCallback(function (string $key, $default) {
if ('credentials.hash' === $key) {
return sha1('old' . 'credentials.login' . 'credentials.salt');
}
return strpos($key, 'credentials') !== false ? $key : $default;
});
$this->container->conf->expects(static::never())->method('set');
$this->container->conf->expects(static::never())->method('write');
$this->container->sessionManager
->expects(static::once())
->method('setSessionParameter')
->with(SessionManager::KEY_ERROR_MESSAGES, ['The old password is not correct.'])
;
$result = $this->controller->change($request, $response);
static::assertSame(400, $result->getStatusCode());
static::assertSame('changepassword', (string) $result->getBody());
static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']);
}
/**
* Change the password with a wrong existing password
*/
public function testPostNewPasswordWrongToken(): void
{
$this->container->sessionManager = $this->createMock(SessionManager::class);
$this->container->sessionManager->method('checkToken')->willReturn(false);
$this->container->conf->expects(static::never())->method('set');
$this->container->conf->expects(static::never())->method('write');
$request = $this->createMock(Request::class);
$response = new Response();
$this->expectException(WrongTokenException::class);
$this->controller->change($request, $response);
}
/**
* Change the password with an empty new password
*/
public function testPostNewEmptyPassword(): void
{
$this->container->sessionManager
->expects(static::once())
->method('setSessionParameter')
->with(SessionManager::KEY_ERROR_MESSAGES, ['You must provide the current and new password to change it.'])
;
$this->container->conf->expects(static::never())->method('set');
$this->container->conf->expects(static::never())->method('write');
$request = $this->createMock(Request::class);
$request->method('getParam')->willReturnCallback(function (string $key): string {
if ('oldpassword' === $key) {
return 'old';
}
if ('setpassword' === $key) {
return '';
}
return $key;
});
$response = new Response();
$result = $this->controller->change($request, $response);
static::assertSame(400, $result->getStatusCode());
static::assertSame('changepassword', (string) $result->getBody());
static::assertSame('Change password - Shaarli', $this->assignedVariables['pagetitle']);
}
}

View file

@ -30,8 +30,6 @@ public function setUp(): void
*/ */
public function testLinksPerPage(): void public function testLinksPerPage(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -62,8 +60,6 @@ public function testLinksPerPage(): void
*/ */
public function testLinksPerPageNotValid(): void public function testLinksPerPageNotValid(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->method('getUri')->willReturnCallback(function (): Uri { $request->method('getUri')->willReturnCallback(function (): Uri {
$uri = $this->createMock(Uri::class); $uri = $this->createMock(Uri::class);
@ -92,8 +88,6 @@ public function testLinksPerPageNotValid(): void
*/ */
public function testVisibility(): void public function testVisibility(): void
{ {
$this->createValidContainerMockSet();
$arg = ['visibility' => 'private']; $arg = ['visibility' => 'private'];
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@ -126,8 +120,6 @@ public function testVisibility(): void
*/ */
public function testVisibilityToggleOff(): void public function testVisibilityToggleOff(): void
{ {
$this->createValidContainerMockSet();
$arg = ['visibility' => 'private']; $arg = ['visibility' => 'private'];
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@ -169,8 +161,6 @@ public function testVisibilityToggleOff(): void
*/ */
public function testVisibilitySwitch(): void public function testVisibilitySwitch(): void
{ {
$this->createValidContainerMockSet();
$arg = ['visibility' => 'private']; $arg = ['visibility' => 'private'];
$this->container->loginManager->method('isLoggedIn')->willReturn(true); $this->container->loginManager->method('isLoggedIn')->willReturn(true);
@ -206,8 +196,6 @@ public function testVisibilitySwitch(): void
*/ */
public function testVisibilityInvalidValue(): void public function testVisibilityInvalidValue(): void
{ {
$this->createValidContainerMockSet();
$arg = ['visibility' => 'test']; $arg = ['visibility' => 'test'];
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@ -244,8 +232,6 @@ public function testVisibilityInvalidValue(): void
*/ */
public function testVisibilityLoggedOut(): void public function testVisibilityLoggedOut(): void
{ {
$this->createValidContainerMockSet();
$arg = ['visibility' => 'test']; $arg = ['visibility' => 'test'];
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@ -283,8 +269,6 @@ public function testVisibilityLoggedOut(): void
*/ */
public function testUntaggedOnly(): void public function testUntaggedOnly(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -314,8 +298,6 @@ public function testUntaggedOnly(): void
*/ */
public function testUntaggedOnlyToggleOff(): void public function testUntaggedOnlyToggleOff(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);

View file

@ -24,8 +24,6 @@ public function setUp(): void
public function testDefaultInvokeWithHttps(): void public function testDefaultInvokeWithHttps(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -49,8 +47,6 @@ public function testDefaultInvokeWithHttps(): void
public function testDefaultInvokeWithoutHttps(): void public function testDefaultInvokeWithoutHttps(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -27,8 +27,6 @@ public function setUp(): void
public function testValidIndexControllerInvokeDefault(): void public function testValidIndexControllerInvokeDefault(): void
{ {
$this->createValidContainerMockSet();
$currentDay = new \DateTimeImmutable('2020-05-13'); $currentDay = new \DateTimeImmutable('2020-05-13');
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -169,8 +167,6 @@ public function testValidIndexControllerInvokeDefault(): void
*/ */
public function testValidIndexControllerInvokeNoFutureOrPast(): void public function testValidIndexControllerInvokeNoFutureOrPast(): void
{ {
$this->createValidContainerMockSet();
$currentDay = new \DateTimeImmutable('2020-05-13'); $currentDay = new \DateTimeImmutable('2020-05-13');
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -243,8 +239,6 @@ public function testValidIndexControllerInvokeNoFutureOrPast(): void
*/ */
public function testValidIndexControllerInvokeHeightAdjustment(): void public function testValidIndexControllerInvokeHeightAdjustment(): void
{ {
$this->createValidContainerMockSet();
$currentDay = new \DateTimeImmutable('2020-05-13'); $currentDay = new \DateTimeImmutable('2020-05-13');
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -314,8 +308,6 @@ public function testValidIndexControllerInvokeHeightAdjustment(): void
*/ */
public function testValidIndexControllerInvokeNoBookmark(): void public function testValidIndexControllerInvokeNoBookmark(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -363,8 +355,6 @@ public function testValidIndexControllerInvokeNoBookmark(): void
*/ */
public function testValidRssControllerInvokeDefault(): void public function testValidRssControllerInvokeDefault(): void
{ {
$this->createValidContainerMockSet();
$dates = [ $dates = [
new \DateTimeImmutable('2020-05-17'), new \DateTimeImmutable('2020-05-17'),
new \DateTimeImmutable('2020-05-15'), new \DateTimeImmutable('2020-05-15'),
@ -439,8 +429,6 @@ public function testValidRssControllerInvokeDefault(): void
*/ */
public function testValidRssControllerInvokeTriggerCache(): void public function testValidRssControllerInvokeTriggerCache(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -465,8 +453,6 @@ public function testValidRssControllerInvokeTriggerCache(): void
*/ */
public function testValidRssControllerInvokeNoBookmark(): void public function testValidRssControllerInvokeNoBookmark(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -30,8 +30,6 @@ public function setUp(): void
*/ */
public function testDefaultRssController(): void public function testDefaultRssController(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -71,8 +69,6 @@ public function testDefaultRssController(): void
*/ */
public function testDefaultAtomController(): void public function testDefaultAtomController(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -112,8 +108,6 @@ public function testDefaultAtomController(): void
*/ */
public function testAtomControllerWithParameters(): void public function testAtomControllerWithParameters(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->method('getParams')->willReturn(['parameter' => 'value']); $request->method('getParams')->willReturn(['parameter' => 'value']);
$response = new Response(); $response = new Response();

View file

@ -31,18 +31,12 @@ trait FrontControllerMockHelper
protected $container; protected $container;
/** /**
* Mock the container instance * Mock the container instance and initialize container's services used by tests
*/ */
protected function createContainer(): void protected function createContainer(): void
{ {
$this->container = $this->createMock(ShaarliTestContainer::class); $this->container = $this->createMock(ShaarliTestContainer::class);
}
/**
* Initialize container's services used by tests
*/
protected function createValidContainerMockSet(): void
{
$this->container->loginManager = $this->createMock(LoginManager::class); $this->container->loginManager = $this->createMock(LoginManager::class);
// Config // Config

View file

@ -26,8 +26,6 @@ public function setUp(): void
public function testValidControllerInvoke(): void public function testValidControllerInvoke(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->expects(static::once())->method('getServerParam')->willReturn('> referer'); $request->expects(static::once())->method('getServerParam')->willReturn('> referer');
$response = new Response(); $response = new Response();
@ -57,8 +55,6 @@ public function testValidControllerInvoke(): void
public function testValidControllerInvokeWithUserName(): void public function testValidControllerInvokeWithUserName(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->expects(static::once())->method('getServerParam')->willReturn('> referer'); $request->expects(static::once())->method('getServerParam')->willReturn('> referer');
$request->expects(static::exactly(2))->method('getParam')->willReturn('myUser>'); $request->expects(static::exactly(2))->method('getParam')->willReturn('myUser>');
@ -90,8 +86,6 @@ public function testValidControllerInvokeWithUserName(): void
public function testLoginControllerWhileLoggedIn(): void public function testLoginControllerWhileLoggedIn(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -106,8 +100,6 @@ public function testLoginControllerWhileLoggedIn(): void
public function testLoginControllerOpenShaarli(): void public function testLoginControllerOpenShaarli(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -129,8 +121,6 @@ public function testLoginControllerOpenShaarli(): void
public function testLoginControllerWhileBanned(): void public function testLoginControllerWhileBanned(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -24,8 +24,6 @@ public function setUp(): void
public function testOpenSearchController(): void public function testOpenSearchController(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -28,8 +28,6 @@ public function setUp(): void
public function testValidControllerInvokeDefault(): void public function testValidControllerInvokeDefault(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request->expects(static::once())->method('getQueryParams')->willReturn([]); $request->expects(static::once())->method('getQueryParams')->willReturn([]);
$response = new Response(); $response = new Response();
@ -106,8 +104,6 @@ public function testControllerWithThumbnailsDisabled(): void
{ {
$this->expectException(ThumbnailsDisabledException::class); $this->expectException(ThumbnailsDisabledException::class);
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -67,8 +67,6 @@ public function redirectFromReferer(
public function testAssignView(): void public function testAssignView(): void
{ {
$this->createValidContainerMockSet();
$this->assignTemplateVars($this->assignedValues); $this->assignTemplateVars($this->assignedValues);
$self = $this->controller->assignView('variableName', 'variableValue'); $self = $this->controller->assignView('variableName', 'variableValue');
@ -79,8 +77,6 @@ public function testAssignView(): void
public function testRender(): void public function testRender(): void
{ {
$this->createValidContainerMockSet();
$this->assignTemplateVars($this->assignedValues); $this->assignTemplateVars($this->assignedValues);
$this->container->bookmarkService $this->container->bookmarkService
@ -120,8 +116,6 @@ public function testRender(): void
*/ */
public function testRedirectFromRefererDefault(): void public function testRedirectFromRefererDefault(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();
@ -137,8 +131,6 @@ public function testRedirectFromRefererDefault(): void
*/ */
public function testRedirectFromRefererWithUnmatchedLoopTerm(): void public function testRedirectFromRefererWithUnmatchedLoopTerm(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();
@ -154,8 +146,6 @@ public function testRedirectFromRefererWithUnmatchedLoopTerm(): void
*/ */
public function testRedirectFromRefererWithMatchingLoopTermInPath(): void public function testRedirectFromRefererWithMatchingLoopTermInPath(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();
@ -171,8 +161,6 @@ public function testRedirectFromRefererWithMatchingLoopTermInPath(): void
*/ */
public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();
@ -189,8 +177,6 @@ public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void
*/ */
public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();
@ -207,8 +193,6 @@ public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void
*/ */
public function testRedirectFromRefererWithLoopTermInDomain(): void public function testRedirectFromRefererWithLoopTermInDomain(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();
@ -225,8 +209,6 @@ public function testRedirectFromRefererWithLoopTermInDomain(): void
*/ */
public function testRedirectFromRefererWithMatchingClearedParam(): void public function testRedirectFromRefererWithMatchingClearedParam(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2'; $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
$response = new Response(); $response = new Response();

View file

@ -28,8 +28,6 @@ public function setUp(): void
*/ */
public function testValidCloudControllerInvokeDefault(): void public function testValidCloudControllerInvokeDefault(): void
{ {
$this->createValidContainerMockSet();
$allTags = [ $allTags = [
'ghi' => 1, 'ghi' => 1,
'abc' => 3, 'abc' => 3,
@ -94,8 +92,6 @@ public function testValidCloudControllerInvokeDefault(): void
*/ */
public function testValidCloudControllerInvokeWithParameters(): void public function testValidCloudControllerInvokeWithParameters(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request $request
->method('getQueryParam') ->method('getQueryParam')
@ -161,8 +157,6 @@ public function testValidCloudControllerInvokeWithParameters(): void
*/ */
public function testEmptyCloud(): void public function testEmptyCloud(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -209,8 +203,6 @@ public function testEmptyCloud(): void
*/ */
public function testValidListControllerInvokeDefault(): void public function testValidListControllerInvokeDefault(): void
{ {
$this->createValidContainerMockSet();
$allTags = [ $allTags = [
'def' => 12, 'def' => 12,
'abc' => 3, 'abc' => 3,
@ -271,8 +263,6 @@ public function testValidListControllerInvokeDefault(): void
*/ */
public function testValidListControllerInvokeWithParameters(): void public function testValidListControllerInvokeWithParameters(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$request $request
->method('getQueryParam') ->method('getQueryParam')
@ -336,8 +326,6 @@ public function testValidListControllerInvokeWithParameters(): void
*/ */
public function testEmptyList(): void public function testEmptyList(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -23,8 +23,6 @@ public function setUp(): void
public function testAddTagWithReferer(): void public function testAddTagWithReferer(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -41,8 +39,6 @@ public function testAddTagWithReferer(): void
public function testAddTagWithRefererAndExistingSearch(): void public function testAddTagWithRefererAndExistingSearch(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -59,8 +55,6 @@ public function testAddTagWithRefererAndExistingSearch(): void
public function testAddTagWithoutRefererAndExistingSearch(): void public function testAddTagWithoutRefererAndExistingSearch(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -75,8 +69,6 @@ public function testAddTagWithoutRefererAndExistingSearch(): void
public function testAddTagRemoveLegacyQueryParam(): void public function testAddTagRemoveLegacyQueryParam(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&addtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&addtag=abc'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -93,8 +85,6 @@ public function testAddTagRemoveLegacyQueryParam(): void
public function testAddTagResetPagination(): void public function testAddTagResetPagination(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&page=12']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&page=12'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -111,8 +101,6 @@ public function testAddTagResetPagination(): void
public function testAddTagWithRefererAndEmptySearch(): void public function testAddTagWithRefererAndEmptySearch(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags='];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -129,8 +117,6 @@ public function testAddTagWithRefererAndEmptySearch(): void
public function testAddTagWithoutNewTagWithReferer(): void public function testAddTagWithoutNewTagWithReferer(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -145,8 +131,6 @@ public function testAddTagWithoutNewTagWithReferer(): void
public function testAddTagWithoutNewTagWithoutReferer(): void public function testAddTagWithoutNewTagWithoutReferer(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -159,8 +143,6 @@ public function testAddTagWithoutNewTagWithoutReferer(): void
public function testRemoveTagWithoutMatchingTag(): void public function testRemoveTagWithoutMatchingTag(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -177,8 +159,6 @@ public function testRemoveTagWithoutMatchingTag(): void
public function testRemoveTagWithoutTagsearch(): void public function testRemoveTagWithoutTagsearch(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -195,8 +175,6 @@ public function testRemoveTagWithoutTagsearch(): void
public function testRemoveTagWithoutReferer(): void public function testRemoveTagWithoutReferer(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();
@ -211,8 +189,6 @@ public function testRemoveTagWithoutReferer(): void
public function testRemoveTagWithoutTag(): void public function testRemoveTagWithoutTag(): void
{ {
$this->createValidContainerMockSet();
$this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtag=abc']; $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtag=abc'];
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
@ -227,8 +203,6 @@ public function testRemoveTagWithoutTag(): void
public function testRemoveTagWithoutTagWithoutReferer(): void public function testRemoveTagWithoutTagWithoutReferer(): void
{ {
$this->createValidContainerMockSet();
$request = $this->createMock(Request::class); $request = $this->createMock(Request::class);
$response = new Response(); $response = new Response();

View file

@ -184,6 +184,20 @@
</div> </div>
{/if} {/if}
{if="!empty($global_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-u-2-24"></div>
<div class="pure-u-20-24">
{loop="$global_errors"}
<p>{$value}</p>
{/loop}
</div>
<div class="pure-u-2-24">
<i class="fa fa-times pure-alert-close"></i>
</div>
</div>
{/if}
{if="!empty($global_warnings) && $is_logged_in"} {if="!empty($global_warnings) && $is_logged_in"}
<div class="pure-g pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert"> <div class="pure-g pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert">
<div class="pure-u-2-24"></div> <div class="pure-u-2-24"></div>
@ -198,4 +212,18 @@
</div> </div>
{/if} {/if}
{if="!empty($global_successes) && $is_logged_in"}
<div class="pure-g new-version-message pure-alert pure-alert-success pure-alert-closable" id="shaarli-success-alert">
<div class="pure-u-2-24"></div>
<div class="pure-u-20-24">
{loop="$global_successes"}
<p>{$value}</p>
{/loop}
</div>
<div class="pure-u-2-24">
<i class="fa fa-times pure-alert-close"></i>
</div>
</div>
{/if}
<div class="clear"></div> <div class="clear"></div>