]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Process password change controller through Slim
authorArthurHoaro <arthur@hoa.ro>
Wed, 27 May 2020 11:35:48 +0000 (13:35 +0200)
committerArthurHoaro <arthur@hoa.ro>
Thu, 23 Jul 2020 19:19:21 +0000 (21:19 +0200)
24 files changed:
application/front/controller/admin/PasswordController.php [new file with mode: 0644]
application/front/controller/admin/ShaarliAdminController.php
application/front/controller/visitor/ShaarliVisitorController.php
application/front/exceptions/OpenShaarliPasswordException.php [new file with mode: 0644]
application/front/exceptions/ShaarliFrontException.php
application/front/exceptions/WrongTokenException.php [new file with mode: 0644]
application/render/PageBuilder.php
application/security/SessionManager.php
index.php
tests/front/controller/admin/FrontAdminControllerMockHelper.php
tests/front/controller/admin/LogoutControllerTest.php
tests/front/controller/admin/PasswordControllerTest.php [new file with mode: 0644]
tests/front/controller/admin/SessionFilterControllerTest.php
tests/front/controller/admin/ToolsControllerTest.php
tests/front/controller/visitor/DailyControllerTest.php
tests/front/controller/visitor/FeedControllerTest.php
tests/front/controller/visitor/FrontControllerMockHelper.php
tests/front/controller/visitor/LoginControllerTest.php
tests/front/controller/visitor/OpenSearchControllerTest.php
tests/front/controller/visitor/PictureWallControllerTest.php
tests/front/controller/visitor/ShaarliPublicControllerTest.php
tests/front/controller/visitor/TagCloudControllerTest.php
tests/front/controller/visitor/TagControllerTest.php
tpl/default/page.header.html

diff --git a/application/front/controller/admin/PasswordController.php b/application/front/controller/admin/PasswordController.php
new file mode 100644 (file)
index 0000000..6e8f0bc
--- /dev/null
@@ -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'));
+    }
+}
index ea703f625ad4c32c8daa36c728a8f46f8e8fd122..3385006c0271446c7d7ab460734cc2361b2941ea 100644 (file)
@@ -7,7 +7,19 @@ namespace Shaarli\Front\Controller\Admin;
 use Shaarli\Container\ShaarliContainer;
 use Shaarli\Front\Controller\Visitor\ShaarliVisitorController;
 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
 {
     public function __construct(ShaarliContainer $container)
@@ -18,4 +30,51 @@ abstract class ShaarliAdminController extends ShaarliVisitorController
             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);
+    }
 }
index 655b3baa294ce2f639163e6ab9a820b744ae4cf7..f12915c135caaf92f57a5a44a2d4ffee11c335f0 100644 (file)
@@ -9,6 +9,14 @@ use Shaarli\Container\ShaarliContainer;
 use Slim\Http\Request;
 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
 {
     /** @var ShaarliContainer */
diff --git a/application/front/exceptions/OpenShaarliPasswordException.php b/application/front/exceptions/OpenShaarliPasswordException.php
new file mode 100644 (file)
index 0000000..a6f0b3a
--- /dev/null
@@ -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);
+    }
+}
index fc8eb92be5d2c0f041fd0ed8814708017e6f0585..73847e6d17bf6ec2a33595766bd5ddce779d7523 100644 (file)
@@ -9,11 +9,11 @@ use Throwable;
 /**
  * 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
  */
-abstract class ShaarliFrontException extends \Exception
+class ShaarliFrontException extends \Exception
 {
     /** Override parent constructor to force $message and $httpCode parameters to be set. */
     public function __construct(string $message, int $httpCode, Throwable $previous = null)
diff --git a/application/front/exceptions/WrongTokenException.php b/application/front/exceptions/WrongTokenException.php
new file mode 100644 (file)
index 0000000..4200272
--- /dev/null
@@ -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);
+    }
+}
index f4fefda84f448297374fc1b5e47ad159f44c1bae..264cd33b613105fc968bb94d210f4e4390dbe3f1 100644 (file)
@@ -7,6 +7,7 @@ use RainTPL;
 use Shaarli\ApplicationUtils;
 use Shaarli\Bookmark\BookmarkServiceInterface;
 use Shaarli\Config\ConfigManager;
+use Shaarli\Security\SessionManager;
 use Shaarli\Thumbnailer;
 
 /**
@@ -136,17 +137,28 @@ class PageBuilder
         $this->tpl->assign('thumbnails_width', $this->conf->get('thumbnails.width'));
         $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'));
 
         // To be removed with a proper theme configuration.
         $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)
      *
@@ -196,6 +208,8 @@ class PageBuilder
             $this->initialize();
         }
 
+        $this->finalize();
+
         $this->tpl->draw($page);
     }
 
@@ -213,6 +227,8 @@ class PageBuilder
             $this->initialize();
         }
 
+        $this->finalize();
+
         return $this->tpl->draw($page, true);
     }
 
index 8b77d362ef817fd7e46891786a48fe2edbc77f13..0ac17d9ab23b6b0c0110033e9b8f2dc49e761db0 100644 (file)
@@ -12,6 +12,10 @@ class SessionManager
     public const KEY_VISIBILITY = 'visibility';
     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 */
     public static $SHORT_TIMEOUT = 3600;    // 1 hour
 
index f4c8b391a44aee1572b57df299421c723e682405..ae56b800425ea96ae7878f3e238bc6eb1559cfb5 100644 (file)
--- a/index.php
+++ b/index.php
@@ -507,56 +507,8 @@ function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionM
 
     // -------- User wants to change his/her password.
     if ($targetPage == Router::$PAGE_CHANGEPASSWORD) {
-        if ($conf->get('security.open_shaarli')) {
-            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;
-            }
-            // 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;
-        }
+        header('Location: ./password');
+        exit;
     }
 
     // -------- User wants to change configuration
@@ -1504,6 +1456,8 @@ $app->group('', function () {
     /* -- LOGGED IN -- */
     $this->get('/logout', '\Shaarli\Front\Controller\Admin\LogoutController:index')->setName('logout');
     $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
         ->get('/links-per-page', '\Shaarli\Front\Controller\Admin\SessionFilterController:linksPerPage')
index 94581c09bc49aadb6d737e13f8684e841c7a2378..bd40c0c7ccf67981a7e4513886f71fa99d5369b8 100644 (file)
@@ -6,7 +6,6 @@ namespace Shaarli\Front\Controller\Admin;
 
 use Shaarli\Container\ShaarliTestContainer;
 use Shaarli\Front\Controller\Visitor\FrontControllerMockHelper;
-use Shaarli\Security\LoginManager;
 
 /**
  * Trait FrontControllerMockHelper
@@ -28,7 +27,7 @@ trait FrontAdminControllerMockHelper
     {
         $this->parentCreateContainer();
 
-        $this->container->loginManager = $this->createMock(LoginManager::class);
         $this->container->loginManager->method('isLoggedIn')->willReturn(true);
+        $this->container->sessionManager->method('checkToken')->willReturn(true);
     }
 }
index ba681b161e8da221c53f5ee781bd7b30adecaf2c..78a0fe731e0de2fdb9102d793068ca479fc2ed7c 100644 (file)
@@ -35,8 +35,6 @@ class LogoutControllerTest extends TestCase
 
     public function testValidControllerInvoke(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
diff --git a/tests/front/controller/admin/PasswordControllerTest.php b/tests/front/controller/admin/PasswordControllerTest.php
new file mode 100644 (file)
index 0000000..7262243
--- /dev/null
@@ -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']);
+    }
+}
index f50f2fc2fbaa348c9ee675efb171b657ced25a42..096963cf46d041b2cd4d45b919e94b4d27459086 100644 (file)
@@ -30,8 +30,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testLinksPerPage(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
 
         $request = $this->createMock(Request::class);
@@ -62,8 +60,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testLinksPerPageNotValid(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request->method('getUri')->willReturnCallback(function (): Uri {
             $uri = $this->createMock(Uri::class);
@@ -92,8 +88,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testVisibility(): void
     {
-        $this->createValidContainerMockSet();
-
         $arg = ['visibility' => 'private'];
 
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@@ -126,8 +120,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testVisibilityToggleOff(): void
     {
-        $this->createValidContainerMockSet();
-
         $arg = ['visibility' => 'private'];
 
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@@ -169,8 +161,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testVisibilitySwitch(): void
     {
-        $this->createValidContainerMockSet();
-
         $arg = ['visibility' => 'private'];
 
         $this->container->loginManager->method('isLoggedIn')->willReturn(true);
@@ -206,8 +196,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testVisibilityInvalidValue(): void
     {
-        $this->createValidContainerMockSet();
-
         $arg = ['visibility' => 'test'];
 
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@@ -244,8 +232,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testVisibilityLoggedOut(): void
     {
-        $this->createValidContainerMockSet();
-
         $arg = ['visibility' => 'test'];
 
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
@@ -283,8 +269,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testUntaggedOnly(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
 
         $request = $this->createMock(Request::class);
@@ -314,8 +298,6 @@ class SessionFilterControllerTest extends TestCase
      */
     public function testUntaggedOnlyToggleOff(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/subfolder/controller/?searchtag=abc'];
 
         $request = $this->createMock(Request::class);
index 47c5746e1f058e1ed27379249f4c0d5af4d97159..fc756f0ffcf75889285014460ccee28722c030bc 100644 (file)
@@ -24,8 +24,6 @@ class ToolsControllerTestControllerTest extends TestCase
 
     public function testDefaultInvokeWithHttps(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -49,8 +47,6 @@ class ToolsControllerTestControllerTest extends TestCase
 
     public function testDefaultInvokeWithoutHttps(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index 6ff769fce57c74c1864f68cfd1f2612c04d6c967..872420fdb6327d0f52c5195bd04a67b119a855c6 100644 (file)
@@ -27,8 +27,6 @@ class DailyControllerTest extends TestCase
 
     public function testValidIndexControllerInvokeDefault(): void
     {
-        $this->createValidContainerMockSet();
-
         $currentDay = new \DateTimeImmutable('2020-05-13');
 
         $request = $this->createMock(Request::class);
@@ -169,8 +167,6 @@ class DailyControllerTest extends TestCase
      */
     public function testValidIndexControllerInvokeNoFutureOrPast(): void
     {
-        $this->createValidContainerMockSet();
-
         $currentDay = new \DateTimeImmutable('2020-05-13');
 
         $request = $this->createMock(Request::class);
@@ -243,8 +239,6 @@ class DailyControllerTest extends TestCase
      */
     public function testValidIndexControllerInvokeHeightAdjustment(): void
     {
-        $this->createValidContainerMockSet();
-
         $currentDay = new \DateTimeImmutable('2020-05-13');
 
         $request = $this->createMock(Request::class);
@@ -314,8 +308,6 @@ class DailyControllerTest extends TestCase
      */
     public function testValidIndexControllerInvokeNoBookmark(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -363,8 +355,6 @@ class DailyControllerTest extends TestCase
      */
     public function testValidRssControllerInvokeDefault(): void
     {
-        $this->createValidContainerMockSet();
-
         $dates = [
             new \DateTimeImmutable('2020-05-17'),
             new \DateTimeImmutable('2020-05-15'),
@@ -439,8 +429,6 @@ class DailyControllerTest extends TestCase
      */
     public function testValidRssControllerInvokeTriggerCache(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -465,8 +453,6 @@ class DailyControllerTest extends TestCase
      */
     public function testValidRssControllerInvokeNoBookmark(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index fd4679ea0816db16c33a7801996dbee21823bb2e..fb417e2a2b152f1a6c52e166c275afda7ab1e190 100644 (file)
@@ -30,8 +30,6 @@ class FeedControllerTest extends TestCase
      */
     public function testDefaultRssController(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -71,8 +69,6 @@ class FeedControllerTest extends TestCase
      */
     public function testDefaultAtomController(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -112,8 +108,6 @@ class FeedControllerTest extends TestCase
      */
     public function testAtomControllerWithParameters(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request->method('getParams')->willReturn(['parameter' => 'value']);
         $response = new Response();
index bc3266b5ce2409ee80bbc072ef88877e3052d16c..d16b6949c21d3f5611399feff93bc8e78f74193f 100644 (file)
@@ -31,18 +31,12 @@ trait FrontControllerMockHelper
     protected $container;
 
     /**
-     * Mock the container instance
+     * Mock the container instance and initialize container's services used by tests
      */
     protected function createContainer(): void
     {
         $this->container = $this->createMock(ShaarliTestContainer::class);
-    }
 
-    /**
-     * Initialize container's services used by tests
-     */
-    protected function createValidContainerMockSet(): void
-    {
         $this->container->loginManager = $this->createMock(LoginManager::class);
 
         // Config
index 9d2233169690cb1521854acd53b732108b1d82ac..faa8ac7131bae68abe9d751cb53db89aeadd73d2 100644 (file)
@@ -26,8 +26,6 @@ class LoginControllerTest extends TestCase
 
     public function testValidControllerInvoke(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request->expects(static::once())->method('getServerParam')->willReturn('> referer');
         $response = new Response();
@@ -57,8 +55,6 @@ class LoginControllerTest extends TestCase
 
     public function testValidControllerInvokeWithUserName(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request->expects(static::once())->method('getServerParam')->willReturn('> referer');
         $request->expects(static::exactly(2))->method('getParam')->willReturn('myUser>');
@@ -90,8 +86,6 @@ class LoginControllerTest extends TestCase
 
     public function testLoginControllerWhileLoggedIn(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -106,8 +100,6 @@ class LoginControllerTest extends TestCase
 
     public function testLoginControllerOpenShaarli(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -129,8 +121,6 @@ class LoginControllerTest extends TestCase
 
     public function testLoginControllerWhileBanned(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index 52475318f6f5c2ea0b961936e4151c306903e19d..5f9f5b1285c42eb72200710a24d8f2fa26c0cd7e 100644 (file)
@@ -24,8 +24,6 @@ class OpenSearchControllerTest extends TestCase
 
     public function testOpenSearchController(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index 7ac842cb2486f6968e171b9124ea4dd545912eac..3dc3f292e3421467e5cc5584450d4f42ff5cae4a 100644 (file)
@@ -28,8 +28,6 @@ class PictureWallControllerTest extends TestCase
 
     public function testValidControllerInvokeDefault(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request->expects(static::once())->method('getQueryParams')->willReturn([]);
         $response = new Response();
@@ -106,8 +104,6 @@ class PictureWallControllerTest extends TestCase
     {
         $this->expectException(ThumbnailsDisabledException::class);
 
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index e2e88da3f64d9d0372b49c2978c9d8deddca1d82..1f7d57ad541bd6a77cd2f257350a297d822f43d7 100644 (file)
@@ -67,8 +67,6 @@ class ShaarliControllerTest extends TestCase
 
     public function testAssignView(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->assignTemplateVars($this->assignedValues);
 
         $self = $this->controller->assignView('variableName', 'variableValue');
@@ -79,8 +77,6 @@ class ShaarliControllerTest extends TestCase
 
     public function testRender(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->assignTemplateVars($this->assignedValues);
 
         $this->container->bookmarkService
@@ -120,8 +116,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererDefault(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
@@ -137,8 +131,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererWithUnmatchedLoopTerm(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
@@ -154,8 +146,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererWithMatchingLoopTermInPath(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
@@ -171,8 +161,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererWithMatchingLoopTermInQueryParam(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
@@ -189,8 +177,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererWithMatchingLoopTermInQueryValue(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
@@ -207,8 +193,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererWithLoopTermInDomain(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
@@ -225,8 +209,6 @@ class ShaarliControllerTest extends TestCase
      */
     public function testRedirectFromRefererWithMatchingClearedParam(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment['HTTP_REFERER'] = 'http://shaarli.tld/subfolder/controller?query=param&other=2';
 
         $response = new Response();
index e636d496a6689e7efde2c257f6d9e65c696ce3eb..9a6a4bc008c386bad89f53d80b4d6ffa6417678d 100644 (file)
@@ -28,8 +28,6 @@ class TagCloudControllerTest extends TestCase
      */
     public function testValidCloudControllerInvokeDefault(): void
     {
-        $this->createValidContainerMockSet();
-
         $allTags = [
             'ghi' => 1,
             'abc' => 3,
@@ -94,8 +92,6 @@ class TagCloudControllerTest extends TestCase
      */
     public function testValidCloudControllerInvokeWithParameters(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request
             ->method('getQueryParam')
@@ -161,8 +157,6 @@ class TagCloudControllerTest extends TestCase
      */
     public function testEmptyCloud(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -209,8 +203,6 @@ class TagCloudControllerTest extends TestCase
      */
     public function testValidListControllerInvokeDefault(): void
     {
-        $this->createValidContainerMockSet();
-
         $allTags = [
             'def' => 12,
             'abc' => 3,
@@ -271,8 +263,6 @@ class TagCloudControllerTest extends TestCase
      */
     public function testValidListControllerInvokeWithParameters(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $request
             ->method('getQueryParam')
@@ -336,8 +326,6 @@ class TagCloudControllerTest extends TestCase
      */
     public function testEmptyList(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index 9a2b1f71efe352feddc840a554fba5b78f6e88a2..1242a2e943ff04cc8edadba9cefc6a5424b20388 100644 (file)
@@ -23,8 +23,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagWithReferer(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/'];
 
         $request = $this->createMock(Request::class);
@@ -41,8 +39,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagWithRefererAndExistingSearch(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def'];
 
         $request = $this->createMock(Request::class);
@@ -59,8 +55,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagWithoutRefererAndExistingSearch(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -75,8 +69,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagRemoveLegacyQueryParam(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&addtag=abc'];
 
         $request = $this->createMock(Request::class);
@@ -93,8 +85,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagResetPagination(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def&page=12'];
 
         $request = $this->createMock(Request::class);
@@ -111,8 +101,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagWithRefererAndEmptySearch(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags='];
 
         $request = $this->createMock(Request::class);
@@ -129,8 +117,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagWithoutNewTagWithReferer(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def'];
 
         $request = $this->createMock(Request::class);
@@ -145,8 +131,6 @@ class TagControllerTest extends TestCase
 
     public function testAddTagWithoutNewTagWithoutReferer(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -159,8 +143,6 @@ class TagControllerTest extends TestCase
 
     public function testRemoveTagWithoutMatchingTag(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtags=def'];
 
         $request = $this->createMock(Request::class);
@@ -177,8 +159,6 @@ class TagControllerTest extends TestCase
 
     public function testRemoveTagWithoutTagsearch(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/'];
 
         $request = $this->createMock(Request::class);
@@ -195,8 +175,6 @@ class TagControllerTest extends TestCase
 
     public function testRemoveTagWithoutReferer(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
@@ -211,8 +189,6 @@ class TagControllerTest extends TestCase
 
     public function testRemoveTagWithoutTag(): void
     {
-        $this->createValidContainerMockSet();
-
         $this->container->environment = ['HTTP_REFERER' => 'http://shaarli/controller/?searchtag=abc'];
 
         $request = $this->createMock(Request::class);
@@ -227,8 +203,6 @@ class TagControllerTest extends TestCase
 
     public function testRemoveTagWithoutTagWithoutReferer(): void
     {
-        $this->createValidContainerMockSet();
-
         $request = $this->createMock(Request::class);
         $response = new Response();
 
index ca7dc1bcd40217f33cb61e1d2b36a979c9022630..4afcca73d41bc83d20b2fc6c43c72eac91854d48 100644 (file)
   </div>
 {/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"}
   <div class="pure-g pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert">
     <div class="pure-u-2-24"></div>
   </div>
 {/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>