Process Shaarli install through Slim controller
This commit is contained in:
parent
1a8ac737e5
commit
c4ad3d4f06
27 changed files with 722 additions and 221 deletions
|
@ -46,6 +46,9 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
/** @var bool true for logged in users. Default value to retrieve private bookmarks. */
|
||||
protected $isLoggedIn;
|
||||
|
||||
/** @var bool Allow datastore alteration from not logged in users. */
|
||||
protected $anonymousPermission = false;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
@ -64,7 +67,7 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
$this->bookmarks = $this->bookmarksIO->read();
|
||||
} catch (EmptyDataStoreException $e) {
|
||||
$this->bookmarks = new BookmarkArray();
|
||||
if ($isLoggedIn) {
|
||||
if ($this->isLoggedIn) {
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +157,7 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
*/
|
||||
public function set($bookmark, $save = true)
|
||||
{
|
||||
if ($this->isLoggedIn !== true) {
|
||||
if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) {
|
||||
throw new Exception(t('You\'re not authorized to alter the datastore'));
|
||||
}
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
|
@ -179,7 +182,7 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
*/
|
||||
public function add($bookmark, $save = true)
|
||||
{
|
||||
if ($this->isLoggedIn !== true) {
|
||||
if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) {
|
||||
throw new Exception(t('You\'re not authorized to alter the datastore'));
|
||||
}
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
|
@ -204,7 +207,7 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
*/
|
||||
public function addOrSet($bookmark, $save = true)
|
||||
{
|
||||
if ($this->isLoggedIn !== true) {
|
||||
if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) {
|
||||
throw new Exception(t('You\'re not authorized to alter the datastore'));
|
||||
}
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
|
@ -221,7 +224,7 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
*/
|
||||
public function remove($bookmark, $save = true)
|
||||
{
|
||||
if ($this->isLoggedIn !== true) {
|
||||
if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) {
|
||||
throw new Exception(t('You\'re not authorized to alter the datastore'));
|
||||
}
|
||||
if (! $bookmark instanceof Bookmark) {
|
||||
|
@ -274,10 +277,11 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
*/
|
||||
public function save()
|
||||
{
|
||||
if (!$this->isLoggedIn) {
|
||||
if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) {
|
||||
// TODO: raise an Exception instead
|
||||
die('You are not authorized to change the database.');
|
||||
}
|
||||
|
||||
$this->bookmarks->reorder();
|
||||
$this->bookmarksIO->write($this->bookmarks);
|
||||
$this->pageCacheManager->invalidateCaches();
|
||||
|
@ -357,6 +361,16 @@ class BookmarkFileService implements BookmarkServiceInterface
|
|||
$initializer->initialize();
|
||||
}
|
||||
|
||||
public function enableAnonymousPermission(): void
|
||||
{
|
||||
$this->anonymousPermission = true;
|
||||
}
|
||||
|
||||
public function disableAnonymousPermission(): void
|
||||
{
|
||||
$this->anonymousPermission = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles migration to the new database format (BookmarksArray).
|
||||
*/
|
||||
|
|
|
@ -34,13 +34,15 @@ class BookmarkInitializer
|
|||
*/
|
||||
public function initialize()
|
||||
{
|
||||
$this->bookmarkService->enableAnonymousPermission();
|
||||
|
||||
$bookmark = new Bookmark();
|
||||
$bookmark->setTitle(t('My secret stuff... - Pastebin.com'));
|
||||
$bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', []);
|
||||
$bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=');
|
||||
$bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.'));
|
||||
$bookmark->setTagsString('secretstuff');
|
||||
$bookmark->setPrivate(true);
|
||||
$this->bookmarkService->add($bookmark);
|
||||
$this->bookmarkService->add($bookmark, false);
|
||||
|
||||
$bookmark = new Bookmark();
|
||||
$bookmark->setTitle(t('The personal, minimalist, super-fast, database free, bookmarking service'));
|
||||
|
@ -54,6 +56,10 @@ To learn how to use Shaarli, consult the link "Documentation" at the bottom of t
|
|||
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.'
|
||||
));
|
||||
$bookmark->setTagsString('opensource software');
|
||||
$this->bookmarkService->add($bookmark);
|
||||
$this->bookmarkService->add($bookmark, false);
|
||||
|
||||
$this->bookmarkService->save();
|
||||
|
||||
$this->bookmarkService->disableAnonymousPermission();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,4 +177,17 @@ interface BookmarkServiceInterface
|
|||
* Creates the default database after a fresh install.
|
||||
*/
|
||||
public function initialize();
|
||||
|
||||
/**
|
||||
* Allow to write the datastore from anonymous session (not logged in).
|
||||
*
|
||||
* This covers a few specific use cases, such as datastore initialization,
|
||||
* but it should be used carefully as it can lead to security issues.
|
||||
*/
|
||||
public function enableAnonymousPermission();
|
||||
|
||||
/**
|
||||
* Disable anonymous permission.
|
||||
*/
|
||||
public function disableAnonymousPermission();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use Shaarli\Netscape\NetscapeBookmarkUtils;
|
|||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Shaarli\Thumbnailer;
|
||||
|
@ -38,6 +39,9 @@ class ContainerBuilder
|
|||
/** @var SessionManager */
|
||||
protected $session;
|
||||
|
||||
/** @var CookieManager */
|
||||
protected $cookieManager;
|
||||
|
||||
/** @var LoginManager */
|
||||
protected $login;
|
||||
|
||||
|
@ -47,11 +51,13 @@ class ContainerBuilder
|
|||
public function __construct(
|
||||
ConfigManager $conf,
|
||||
SessionManager $session,
|
||||
CookieManager $cookieManager,
|
||||
LoginManager $login
|
||||
) {
|
||||
$this->conf = $conf;
|
||||
$this->session = $session;
|
||||
$this->login = $login;
|
||||
$this->cookieManager = $cookieManager;
|
||||
}
|
||||
|
||||
public function build(): ShaarliContainer
|
||||
|
@ -60,6 +66,7 @@ class ContainerBuilder
|
|||
|
||||
$container['conf'] = $this->conf;
|
||||
$container['sessionManager'] = $this->session;
|
||||
$container['cookieManager'] = $this->cookieManager;
|
||||
$container['loginManager'] = $this->login;
|
||||
$container['basePath'] = $this->basePath;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shaarli\Container;
|
||||
|
||||
use http\Cookie;
|
||||
use Shaarli\Bookmark\BookmarkServiceInterface;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Feed\FeedBuilder;
|
||||
|
@ -14,6 +15,7 @@ use Shaarli\Netscape\NetscapeBookmarkUtils;
|
|||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Shaarli\Thumbnailer;
|
||||
|
@ -25,6 +27,7 @@ use Slim\Container;
|
|||
*
|
||||
* @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`)
|
||||
* @property BookmarkServiceInterface $bookmarkService
|
||||
* @property CookieManager $cookieManager
|
||||
* @property ConfigManager $conf
|
||||
* @property mixed[] $environment $_SERVER automatically injected by Slim
|
||||
* @property callable $errorHandler Overrides default Slim error display
|
||||
|
|
|
@ -43,6 +43,12 @@ class ShaarliMiddleware
|
|||
$this->container->basePath = rtrim($request->getUri()->getBasePath(), '/');
|
||||
|
||||
try {
|
||||
if (!is_file($this->container->conf->getConfigFileExt())
|
||||
&& !in_array($next->getName(), ['displayInstall', 'saveInstall'], true)
|
||||
) {
|
||||
return $response->withRedirect($this->container->basePath . '/install');
|
||||
}
|
||||
|
||||
$this->runUpdates();
|
||||
$this->checkOpenShaarli($request, $response, $next);
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shaarli\Front\Controller\Admin;
|
||||
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
@ -20,9 +21,12 @@ class LogoutController extends ShaarliAdminController
|
|||
{
|
||||
$this->container->pageCacheManager->invalidateCaches();
|
||||
$this->container->sessionManager->logout();
|
||||
|
||||
// TODO: switch to a simple Cookie manager allowing to check the session, and create mocks.
|
||||
setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, $this->container->basePath . '/');
|
||||
$this->container->cookieManager->setCookieParameter(
|
||||
CookieManager::STAY_SIGNED_IN,
|
||||
'false',
|
||||
0,
|
||||
$this->container->basePath . '/'
|
||||
);
|
||||
|
||||
return $this->redirect($response, '/');
|
||||
}
|
||||
|
|
173
application/front/controller/visitor/InstallController.php
Normal file
173
application/front/controller/visitor/InstallController.php
Normal file
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Front\Controller\Visitor;
|
||||
|
||||
use Shaarli\ApplicationUtils;
|
||||
use Shaarli\Bookmark\BookmarkFilter;
|
||||
use Shaarli\Container\ShaarliContainer;
|
||||
use Shaarli\Front\Exception\AlreadyInstalledException;
|
||||
use Shaarli\Front\Exception\ResourcePermissionException;
|
||||
use Shaarli\Languages;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
/**
|
||||
* Slim controller used to render install page, and create initial configuration file.
|
||||
*/
|
||||
class InstallController extends ShaarliVisitorController
|
||||
{
|
||||
public const SESSION_TEST_KEY = 'session_tested';
|
||||
public const SESSION_TEST_VALUE = 'Working';
|
||||
|
||||
public function __construct(ShaarliContainer $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
|
||||
if (is_file($this->container->conf->getConfigFileExt())) {
|
||||
throw new AlreadyInstalledException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the install template page.
|
||||
* Also test file permissions and sessions beforehand.
|
||||
*/
|
||||
public function index(Request $request, Response $response): Response
|
||||
{
|
||||
// Before installation, we'll make sure that permissions are set properly, and sessions are working.
|
||||
$this->checkPermissions();
|
||||
|
||||
if (static::SESSION_TEST_VALUE
|
||||
!== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
|
||||
) {
|
||||
$this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE);
|
||||
|
||||
return $this->redirect($response, '/install/session-test');
|
||||
}
|
||||
|
||||
[$continents, $cities] = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
|
||||
|
||||
$this->assignView('continents', $continents);
|
||||
$this->assignView('cities', $cities);
|
||||
$this->assignView('languages', Languages::getAvailableLanguages());
|
||||
|
||||
return $response->write($this->render('install'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Route checking that the session parameter has been properly saved between two distinct requests.
|
||||
* If the session parameter is preserved, redirect to install template page, otherwise displays error.
|
||||
*/
|
||||
public function sessionTest(Request $request, Response $response): Response
|
||||
{
|
||||
// This part makes sure sessions works correctly.
|
||||
// (Because on some hosts, session.save_path may not be set correctly,
|
||||
// or we may not have write access to it.)
|
||||
if (static::SESSION_TEST_VALUE
|
||||
!== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY)
|
||||
) {
|
||||
// Step 2: Check if data in session is correct.
|
||||
$msg = t(
|
||||
'<pre>Sessions do not seem to work correctly on your server.<br>'.
|
||||
'Make sure the variable "session.save_path" is set correctly in your PHP config, '.
|
||||
'and that you have write access to it.<br>'.
|
||||
'It currently points to %s.<br>'.
|
||||
'On some browsers, accessing your server via a hostname like \'localhost\' '.
|
||||
'or any custom hostname without a dot causes cookie storage to fail. '.
|
||||
'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>'
|
||||
);
|
||||
$msg = sprintf($msg, $this->container->sessionManager->getSavePath());
|
||||
|
||||
$this->assignView('message', $msg);
|
||||
|
||||
return $response->write($this->render('error'));
|
||||
}
|
||||
|
||||
return $this->redirect($response, '/install');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save installation form and initialize config file and datastore if necessary.
|
||||
*/
|
||||
public function save(Request $request, Response $response): Response
|
||||
{
|
||||
$timezone = 'UTC';
|
||||
if (!empty($request->getParam('continent'))
|
||||
&& !empty($request->getParam('city'))
|
||||
&& isTimeZoneValid($request->getParam('continent'), $request->getParam('city'))
|
||||
) {
|
||||
$timezone = $request->getParam('continent') . '/' . $request->getParam('city');
|
||||
}
|
||||
$this->container->conf->set('general.timezone', $timezone);
|
||||
|
||||
$login = $request->getParam('setlogin');
|
||||
$this->container->conf->set('credentials.login', $login);
|
||||
$salt = sha1(uniqid('', true) .'_'. mt_rand());
|
||||
$this->container->conf->set('credentials.salt', $salt);
|
||||
$this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt));
|
||||
|
||||
if (!empty($request->getParam('title'))) {
|
||||
$this->container->conf->set('general.title', escape($request->getParam('title')));
|
||||
} else {
|
||||
$this->container->conf->set(
|
||||
'general.title',
|
||||
'Shared bookmarks on '.escape(index_url($this->container->environment))
|
||||
);
|
||||
}
|
||||
|
||||
$this->container->conf->set('translation.language', escape($request->getParam('language')));
|
||||
$this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck')));
|
||||
$this->container->conf->set('api.enabled', !empty($request->getParam('enableApi')));
|
||||
$this->container->conf->set(
|
||||
'api.secret',
|
||||
generate_api_secret(
|
||||
$this->container->conf->get('credentials.login'),
|
||||
$this->container->conf->get('credentials.salt')
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
// Everything is ok, let's create config file.
|
||||
$this->container->conf->write($this->container->loginManager->isLoggedIn());
|
||||
} catch (\Exception $e) {
|
||||
$this->assignView('message', $e->getMessage());
|
||||
$this->assignView('stacktrace', $e->getTraceAsString());
|
||||
|
||||
return $response->write($this->render('error'));
|
||||
}
|
||||
|
||||
if ($this->container->bookmarkService->count(BookmarkFilter::$ALL) === 0) {
|
||||
$this->container->bookmarkService->initialize();
|
||||
}
|
||||
|
||||
$this->container->sessionManager->setSessionParameter(
|
||||
SessionManager::KEY_SUCCESS_MESSAGES,
|
||||
[t('Shaarli is now configured. Please login and start shaaring your bookmarks!')]
|
||||
);
|
||||
|
||||
return $this->redirect($response, '/');
|
||||
}
|
||||
|
||||
protected function checkPermissions(): bool
|
||||
{
|
||||
// Ensure Shaarli has proper access to its resources
|
||||
$errors = ApplicationUtils::checkResourcePermissions($this->container->conf);
|
||||
|
||||
if (empty($errors)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME! Do not insert HTML here.
|
||||
$message = '<p>'. t('Insufficient permissions:') .'</p><ul>';
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$message .= '<li>'.$error.'</li>';
|
||||
}
|
||||
$message .= '</ul>';
|
||||
|
||||
throw new ResourcePermissionException($message);
|
||||
}
|
||||
}
|
15
application/front/exceptions/AlreadyInstalledException.php
Normal file
15
application/front/exceptions/AlreadyInstalledException.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Front\Exception;
|
||||
|
||||
class AlreadyInstalledException extends ShaarliFrontException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$message = t('Shaarli has already been installed. Login to edit the configuration.');
|
||||
|
||||
parent::__construct($message, 401);
|
||||
}
|
||||
}
|
13
application/front/exceptions/ResourcePermissionException.php
Normal file
13
application/front/exceptions/ResourcePermissionException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Front\Exception;
|
||||
|
||||
class ResourcePermissionException extends ShaarliFrontException
|
||||
{
|
||||
public function __construct(string $message)
|
||||
{
|
||||
parent::__construct($message, 500);
|
||||
}
|
||||
}
|
33
application/security/CookieManager.php
Normal file
33
application/security/CookieManager.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Security;
|
||||
|
||||
class CookieManager
|
||||
{
|
||||
/** @var string Name of the cookie set after logging in **/
|
||||
public const STAY_SIGNED_IN = 'shaarli_staySignedIn';
|
||||
|
||||
/** @var mixed $_COOKIE set by reference */
|
||||
protected $cookies;
|
||||
|
||||
public function __construct(array &$cookies)
|
||||
{
|
||||
$this->cookies = $cookies;
|
||||
}
|
||||
|
||||
public function setCookieParameter(string $key, string $value, int $expires, string $path): self
|
||||
{
|
||||
$this->cookies[$key] = $value;
|
||||
|
||||
setcookie($key, $value, $expires, $path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCookieParameter(string $key, string $default = null): ?string
|
||||
{
|
||||
return $this->cookies[$key] ?? $default;
|
||||
}
|
||||
}
|
|
@ -9,9 +9,6 @@ use Shaarli\Config\ConfigManager;
|
|||
*/
|
||||
class LoginManager
|
||||
{
|
||||
/** @var string Name of the cookie set after logging in **/
|
||||
public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn';
|
||||
|
||||
/** @var array A reference to the $_GLOBALS array */
|
||||
protected $globals = [];
|
||||
|
||||
|
@ -32,17 +29,21 @@ class LoginManager
|
|||
|
||||
/** @var string User sign-in token depending on remote IP and credentials */
|
||||
protected $staySignedInToken = '';
|
||||
/** @var CookieManager */
|
||||
protected $cookieManager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ConfigManager $configManager Configuration Manager instance
|
||||
* @param SessionManager $sessionManager SessionManager instance
|
||||
* @param CookieManager $cookieManager CookieManager instance
|
||||
*/
|
||||
public function __construct($configManager, $sessionManager)
|
||||
public function __construct($configManager, $sessionManager, $cookieManager)
|
||||
{
|
||||
$this->configManager = $configManager;
|
||||
$this->sessionManager = $sessionManager;
|
||||
$this->cookieManager = $cookieManager;
|
||||
$this->banManager = new BanManager(
|
||||
$this->configManager->get('security.trusted_proxies', []),
|
||||
$this->configManager->get('security.ban_after'),
|
||||
|
@ -86,10 +87,9 @@ class LoginManager
|
|||
/**
|
||||
* Check user session state and validity (expiration)
|
||||
*
|
||||
* @param array $cookie The $_COOKIE array
|
||||
* @param string $clientIpId Client IP address identifier
|
||||
*/
|
||||
public function checkLoginState($cookie, $clientIpId)
|
||||
public function checkLoginState($clientIpId)
|
||||
{
|
||||
if (! $this->configManager->exists('credentials.login')) {
|
||||
// Shaarli is not configured yet
|
||||
|
@ -97,9 +97,7 @@ class LoginManager
|
|||
return;
|
||||
}
|
||||
|
||||
if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE])
|
||||
&& $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
|
||||
) {
|
||||
if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) {
|
||||
// The user client has a valid stay-signed-in cookie
|
||||
// Session information is updated with the current client information
|
||||
$this->sessionManager->storeLoginInfo($clientIpId);
|
||||
|
|
|
@ -31,16 +31,21 @@ class SessionManager
|
|||
/** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */
|
||||
protected $staySignedIn = false;
|
||||
|
||||
/** @var string */
|
||||
protected $savePath;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array $session The $_SESSION array (reference)
|
||||
* @param ConfigManager $conf ConfigManager instance
|
||||
* @param array $session The $_SESSION array (reference)
|
||||
* @param ConfigManager $conf ConfigManager instance
|
||||
* @param string $savePath Session save path returned by builtin function session_save_path()
|
||||
*/
|
||||
public function __construct(& $session, $conf)
|
||||
public function __construct(&$session, $conf, string $savePath)
|
||||
{
|
||||
$this->session = &$session;
|
||||
$this->conf = $conf;
|
||||
$this->savePath = $savePath;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -249,4 +254,9 @@ class SessionManager
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSavePath(): string
|
||||
{
|
||||
return $this->savePath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,11 @@ class Updater
|
|||
*/
|
||||
protected $methods;
|
||||
|
||||
/**
|
||||
* @var string $basePath Shaarli root directory (from HTTP Request)
|
||||
*/
|
||||
protected $basePath = null;
|
||||
|
||||
/**
|
||||
* Object constructor.
|
||||
*
|
||||
|
@ -62,11 +67,13 @@ class Updater
|
|||
* Run all new updates.
|
||||
* Update methods have to start with 'updateMethod' and return true (on success).
|
||||
*
|
||||
* @param string $basePath Shaarli root directory (from HTTP Request)
|
||||
*
|
||||
* @return array An array containing ran updates.
|
||||
*
|
||||
* @throws UpdaterException If something went wrong.
|
||||
*/
|
||||
public function update()
|
||||
public function update(string $basePath = null)
|
||||
{
|
||||
$updatesRan = [];
|
||||
|
||||
|
@ -123,16 +130,14 @@ class Updater
|
|||
}
|
||||
|
||||
/**
|
||||
* With the Slim routing system, default header link should be `./` instead of `?`.
|
||||
* Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`.
|
||||
* With the Slim routing system, default header link should be `/subfolder/` instead of `?`.
|
||||
* Otherwise you can not go back to the home page.
|
||||
* Example: `/subfolder/picture-wall` -> `/subfolder/picture-wall?` instead of `/subfolder/`.
|
||||
*/
|
||||
public function updateMethodRelativeHomeLink(): bool
|
||||
{
|
||||
$link = trim($this->conf->get('general.header_link'));
|
||||
if ($link[0] === '?') {
|
||||
$link = './'. ltrim($link, '?');
|
||||
|
||||
$this->conf->set('general.header_link', $link, true, true);
|
||||
if ('?' === trim($this->conf->get('general.header_link'))) {
|
||||
$this->conf->set('general.header_link', $this->basePath . '/', true, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -152,7 +157,7 @@ class Updater
|
|||
&& 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match)
|
||||
) {
|
||||
$updated = true;
|
||||
$bookmark = $bookmark->setUrl('/shaare/' . $match[1]);
|
||||
$bookmark = $bookmark->setUrl($this->basePath . '/shaare/' . $match[1]);
|
||||
|
||||
$this->bookmarkService->set($bookmark, false);
|
||||
}
|
||||
|
@ -164,4 +169,11 @@ class Updater
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setBasePath(string $basePath): self
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
153
index.php
153
index.php
|
@ -61,13 +61,11 @@ require_once 'application/TimeZone.php';
|
|||
require_once 'application/Utils.php';
|
||||
|
||||
use Shaarli\ApplicationUtils;
|
||||
use Shaarli\Bookmark\BookmarkFileService;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Container\ContainerBuilder;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Languages;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Slim\App;
|
||||
|
@ -118,13 +116,14 @@ if ($conf->get('dev.debug', false)) {
|
|||
// See all errors (for debugging only)
|
||||
error_reporting(-1);
|
||||
|
||||
set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
|
||||
set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext) {
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
});
|
||||
}
|
||||
|
||||
$sessionManager = new SessionManager($_SESSION, $conf);
|
||||
$loginManager = new LoginManager($conf, $sessionManager);
|
||||
$sessionManager = new SessionManager($_SESSION, $conf, session_save_path());
|
||||
$cookieManager = new CookieManager($_COOKIE);
|
||||
$loginManager = new LoginManager($conf, $sessionManager, $cookieManager);
|
||||
$loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
|
||||
$clientIpId = client_ip_id($_SERVER);
|
||||
|
||||
|
@ -158,28 +157,7 @@ header("Cache-Control: no-store, no-cache, must-revalidate");
|
|||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
|
||||
if (! is_file($conf->getConfigFileExt())) {
|
||||
// Ensure Shaarli has proper access to its resources
|
||||
$errors = ApplicationUtils::checkResourcePermissions($conf);
|
||||
|
||||
if ($errors != array()) {
|
||||
$message = '<p>'. t('Insufficient permissions:') .'</p><ul>';
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$message .= '<li>'.$error.'</li>';
|
||||
}
|
||||
$message .= '</ul>';
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo $message;
|
||||
exit;
|
||||
}
|
||||
|
||||
// Display the installation form if no existing config is found
|
||||
install($conf, $sessionManager, $loginManager);
|
||||
}
|
||||
|
||||
$loginManager->checkLoginState($_COOKIE, $clientIpId);
|
||||
$loginManager->checkLoginState($clientIpId);
|
||||
|
||||
// ------------------------------------------------------------------------------------------
|
||||
// Process login form: Check if login/password is correct.
|
||||
|
@ -205,7 +183,7 @@ if (isset($_POST['login'])) {
|
|||
$expirationTime = $sessionManager->extendSession();
|
||||
|
||||
setcookie(
|
||||
$loginManager::$STAY_SIGNED_IN_COOKIE,
|
||||
CookieManager::STAY_SIGNED_IN,
|
||||
$loginManager->getStaySignedInToken(),
|
||||
$expirationTime,
|
||||
WEB_PATH
|
||||
|
@ -271,122 +249,11 @@ if (!isset($_SESSION['tokens'])) {
|
|||
$_SESSION['tokens']=array(); // Token are attached to the session.
|
||||
}
|
||||
|
||||
/**
|
||||
* Installation
|
||||
* This function should NEVER be called if the file data/config.php exists.
|
||||
*
|
||||
* @param ConfigManager $conf Configuration Manager instance.
|
||||
* @param SessionManager $sessionManager SessionManager instance
|
||||
* @param LoginManager $loginManager LoginManager instance
|
||||
*/
|
||||
function install($conf, $sessionManager, $loginManager)
|
||||
{
|
||||
// On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
|
||||
if (endsWith($_SERVER['HTTP_HOST'], '.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) {
|
||||
mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions', 0705);
|
||||
}
|
||||
|
||||
|
||||
// This part makes sure sessions works correctly.
|
||||
// (Because on some hosts, session.save_path may not be set correctly,
|
||||
// or we may not have write access to it.)
|
||||
if (isset($_GET['test_session'])
|
||||
&& ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) {
|
||||
// Step 2: Check if data in session is correct.
|
||||
$msg = t(
|
||||
'<pre>Sessions do not seem to work correctly on your server.<br>'.
|
||||
'Make sure the variable "session.save_path" is set correctly in your PHP config, '.
|
||||
'and that you have write access to it.<br>'.
|
||||
'It currently points to %s.<br>'.
|
||||
'On some browsers, accessing your server via a hostname like \'localhost\' '.
|
||||
'or any custom hostname without a dot causes cookie storage to fail. '.
|
||||
'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>'
|
||||
);
|
||||
$msg = sprintf($msg, session_save_path());
|
||||
echo $msg;
|
||||
echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>';
|
||||
die;
|
||||
}
|
||||
if (!isset($_SESSION['session_tested'])) {
|
||||
// Step 1 : Try to store data in session and reload page.
|
||||
$_SESSION['session_tested'] = 'Working'; // Try to set a variable in session.
|
||||
header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data.
|
||||
}
|
||||
if (isset($_GET['test_session'])) {
|
||||
// Step 3: Sessions are OK. Remove test parameter from URL.
|
||||
header('Location: '.index_url($_SERVER));
|
||||
}
|
||||
|
||||
|
||||
if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) {
|
||||
$tz = 'UTC';
|
||||
if (!empty($_POST['continent']) && !empty($_POST['city'])
|
||||
&& isTimeZoneValid($_POST['continent'], $_POST['city'])
|
||||
) {
|
||||
$tz = $_POST['continent'].'/'.$_POST['city'];
|
||||
}
|
||||
$conf->set('general.timezone', $tz);
|
||||
$login = $_POST['setlogin'];
|
||||
$conf->set('credentials.login', $login);
|
||||
$salt = sha1(uniqid('', true) .'_'. mt_rand());
|
||||
$conf->set('credentials.salt', $salt);
|
||||
$conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt));
|
||||
if (!empty($_POST['title'])) {
|
||||
$conf->set('general.title', escape($_POST['title']));
|
||||
} else {
|
||||
$conf->set('general.title', 'Shared bookmarks on '.escape(index_url($_SERVER)));
|
||||
}
|
||||
$conf->set('translation.language', escape($_POST['language']));
|
||||
$conf->set('updates.check_updates', !empty($_POST['updateCheck']));
|
||||
$conf->set('api.enabled', !empty($_POST['enableApi']));
|
||||
$conf->set(
|
||||
'api.secret',
|
||||
generate_api_secret(
|
||||
$conf->get('credentials.login'),
|
||||
$conf->get('credentials.salt')
|
||||
)
|
||||
);
|
||||
try {
|
||||
// Everything is ok, let's create config file.
|
||||
$conf->write($loginManager->isLoggedIn());
|
||||
} catch (Exception $e) {
|
||||
error_log(
|
||||
'ERROR while writing config file after installation.' . PHP_EOL .
|
||||
$e->getMessage()
|
||||
);
|
||||
|
||||
// TODO: do not handle exceptions/errors in JS.
|
||||
echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
|
||||
exit;
|
||||
}
|
||||
|
||||
$history = new History($conf->get('resource.history'));
|
||||
$bookmarkService = new BookmarkFileService($conf, $history, true);
|
||||
if ($bookmarkService->count() === 0) {
|
||||
$bookmarkService->initialize();
|
||||
}
|
||||
|
||||
echo '<script>alert('
|
||||
.'"Shaarli is now configured. '
|
||||
.'Please enter your login/password and start shaaring your bookmarks!"'
|
||||
.');document.location=\'./login\';</script>';
|
||||
exit;
|
||||
}
|
||||
|
||||
$PAGE = new PageBuilder($conf, $_SESSION, null, $sessionManager->generateToken());
|
||||
list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
|
||||
$PAGE->assign('continents', $continents);
|
||||
$PAGE->assign('cities', $cities);
|
||||
$PAGE->assign('languages', Languages::getAvailableLanguages());
|
||||
$PAGE->renderPage('install');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['LINKS_PER_PAGE'])) {
|
||||
$_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20);
|
||||
}
|
||||
|
||||
$containerBuilder = new ContainerBuilder($conf, $sessionManager, $loginManager);
|
||||
$containerBuilder = new ContainerBuilder($conf, $sessionManager, $cookieManager, $loginManager);
|
||||
$container = $containerBuilder->build();
|
||||
$app = new App($container);
|
||||
|
||||
|
@ -408,6 +275,10 @@ $app->group('/api/v1', function () {
|
|||
})->add('\Shaarli\Api\ApiMiddleware');
|
||||
|
||||
$app->group('', function () {
|
||||
$this->get('/install', '\Shaarli\Front\Controller\Visitor\InstallController:index')->setName('displayInstall');
|
||||
$this->get('/install/session-test', '\Shaarli\Front\Controller\Visitor\InstallController:sessionTest');
|
||||
$this->post('/install', '\Shaarli\Front\Controller\Visitor\InstallController:save')->setName('saveInstall');
|
||||
|
||||
/* -- PUBLIC --*/
|
||||
$this->get('/', '\Shaarli\Front\Controller\Visitor\BookmarkListController:index');
|
||||
$this->get('/shaare/{hash}', '\Shaarli\Front\Controller\Visitor\BookmarkListController:permalink');
|
||||
|
|
|
@ -18,9 +18,14 @@ require_once 'application/bookmark/LinkUtils.php';
|
|||
require_once 'application/Utils.php';
|
||||
require_once 'application/http/UrlUtils.php';
|
||||
require_once 'application/http/HttpUtils.php';
|
||||
require_once 'tests/utils/ReferenceLinkDB.php';
|
||||
require_once 'tests/utils/ReferenceHistory.php';
|
||||
require_once 'tests/utils/FakeBookmarkService.php';
|
||||
require_once 'tests/container/ShaarliTestContainer.php';
|
||||
require_once 'tests/front/controller/visitor/FrontControllerMockHelper.php';
|
||||
require_once 'tests/front/controller/admin/FrontAdminControllerMockHelper.php';
|
||||
require_once 'tests/updater/DummyUpdater.php';
|
||||
require_once 'tests/utils/FakeBookmarkService.php';
|
||||
require_once 'tests/utils/FakeConfigManager.php';
|
||||
require_once 'tests/utils/ReferenceHistory.php';
|
||||
require_once 'tests/utils/ReferenceLinkDB.php';
|
||||
require_once 'tests/utils/ReferenceSessionIdHashes.php';
|
||||
|
||||
\ReferenceSessionIdHashes::genAllHashes();
|
||||
|
|
|
@ -11,12 +11,15 @@ use Shaarli\Feed\FeedBuilder;
|
|||
use Shaarli\Formatter\FormatterFactory;
|
||||
use Shaarli\History;
|
||||
use Shaarli\Http\HttpAccess;
|
||||
use Shaarli\Netscape\NetscapeBookmarkUtils;
|
||||
use Shaarli\Plugin\PluginManager;
|
||||
use Shaarli\Render\PageBuilder;
|
||||
use Shaarli\Render\PageCacheManager;
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Shaarli\Thumbnailer;
|
||||
use Shaarli\Updater\Updater;
|
||||
|
||||
class ContainerBuilderTest extends TestCase
|
||||
{
|
||||
|
@ -32,10 +35,14 @@ class ContainerBuilderTest extends TestCase
|
|||
/** @var ContainerBuilder */
|
||||
protected $containerBuilder;
|
||||
|
||||
/** @var CookieManager */
|
||||
protected $cookieManager;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->conf = new ConfigManager('tests/utils/config/configJson');
|
||||
$this->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->cookieManager = $this->createMock(CookieManager::class);
|
||||
|
||||
$this->loginManager = $this->createMock(LoginManager::class);
|
||||
$this->loginManager->method('isLoggedIn')->willReturn(true);
|
||||
|
@ -43,6 +50,7 @@ class ContainerBuilderTest extends TestCase
|
|||
$this->containerBuilder = new ContainerBuilder(
|
||||
$this->conf,
|
||||
$this->sessionManager,
|
||||
$this->cookieManager,
|
||||
$this->loginManager
|
||||
);
|
||||
}
|
||||
|
@ -53,6 +61,7 @@ class ContainerBuilderTest extends TestCase
|
|||
|
||||
static::assertInstanceOf(ConfigManager::class, $container->conf);
|
||||
static::assertInstanceOf(SessionManager::class, $container->sessionManager);
|
||||
static::assertInstanceOf(CookieManager::class, $container->cookieManager);
|
||||
static::assertInstanceOf(LoginManager::class, $container->loginManager);
|
||||
static::assertInstanceOf(History::class, $container->history);
|
||||
static::assertInstanceOf(BookmarkServiceInterface::class, $container->bookmarkService);
|
||||
|
@ -63,6 +72,8 @@ class ContainerBuilderTest extends TestCase
|
|||
static::assertInstanceOf(FeedBuilder::class, $container->feedBuilder);
|
||||
static::assertInstanceOf(Thumbnailer::class, $container->thumbnailer);
|
||||
static::assertInstanceOf(HttpAccess::class, $container->httpAccess);
|
||||
static::assertInstanceOf(NetscapeBookmarkUtils::class, $container->netscapeBookmarkUtils);
|
||||
static::assertInstanceOf(Updater::class, $container->updater);
|
||||
|
||||
// Set by the middleware
|
||||
static::assertNull($container->basePath);
|
||||
|
|
|
@ -19,6 +19,8 @@ use Slim\Http\Uri;
|
|||
|
||||
class ShaarliMiddlewareTest extends TestCase
|
||||
{
|
||||
protected const TMP_MOCK_FILE = '.tmp';
|
||||
|
||||
/** @var ShaarliContainer */
|
||||
protected $container;
|
||||
|
||||
|
@ -29,12 +31,21 @@ class ShaarliMiddlewareTest extends TestCase
|
|||
{
|
||||
$this->container = $this->createMock(ShaarliContainer::class);
|
||||
|
||||
touch(static::TMP_MOCK_FILE);
|
||||
|
||||
$this->container->conf = $this->createMock(ConfigManager::class);
|
||||
$this->container->conf->method('getConfigFileExt')->willReturn(static::TMP_MOCK_FILE);
|
||||
|
||||
$this->container->loginManager = $this->createMock(LoginManager::class);
|
||||
|
||||
$this->middleware = new ShaarliMiddleware($this->container);
|
||||
}
|
||||
|
||||
public function tearDown()
|
||||
{
|
||||
unlink(static::TMP_MOCK_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test middleware execution with valid controller call
|
||||
*/
|
||||
|
@ -179,6 +190,7 @@ class ShaarliMiddlewareTest extends TestCase
|
|||
$this->container->conf->method('get')->willReturnCallback(function (string $key): string {
|
||||
return $key;
|
||||
});
|
||||
$this->container->conf->method('getConfigFileExt')->willReturn(static::TMP_MOCK_FILE);
|
||||
|
||||
$this->container->pageCacheManager = $this->createMock(PageCacheManager::class);
|
||||
$this->container->pageCacheManager->expects(static::once())->method('invalidateCaches');
|
||||
|
|
|
@ -4,14 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shaarli\Front\Controller\Admin;
|
||||
|
||||
/** Override PHP builtin setcookie function in the local namespace to mock it... more or less */
|
||||
if (!function_exists('Shaarli\Front\Controller\Admin\setcookie')) {
|
||||
function setcookie(string $name, string $value): void {
|
||||
$_COOKIE[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shaarli\Security\CookieManager;
|
||||
use Shaarli\Security\LoginManager;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Slim\Http\Request;
|
||||
|
@ -29,8 +23,6 @@ class LogoutControllerTest extends TestCase
|
|||
$this->createContainer();
|
||||
|
||||
$this->controller = new LogoutController($this->container);
|
||||
|
||||
setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, $cookie = 'hi there');
|
||||
}
|
||||
|
||||
public function testValidControllerInvoke(): void
|
||||
|
@ -43,13 +35,17 @@ class LogoutControllerTest extends TestCase
|
|||
$this->container->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->container->sessionManager->expects(static::once())->method('logout');
|
||||
|
||||
static::assertSame('hi there', $_COOKIE[LoginManager::$STAY_SIGNED_IN_COOKIE]);
|
||||
$this->container->cookieManager = $this->createMock(CookieManager::class);
|
||||
$this->container->cookieManager
|
||||
->expects(static::once())
|
||||
->method('setCookieParameter')
|
||||
->with(CookieManager::STAY_SIGNED_IN, 'false', 0, '/subfolder/')
|
||||
;
|
||||
|
||||
$result = $this->controller->index($request, $response);
|
||||
|
||||
static::assertInstanceOf(Response::class, $result);
|
||||
static::assertSame(302, $result->getStatusCode());
|
||||
static::assertSame(['/subfolder/'], $result->getHeader('location'));
|
||||
static::assertSame('false', $_COOKIE[LoginManager::$STAY_SIGNED_IN_COOKIE]);
|
||||
}
|
||||
}
|
||||
|
|
264
tests/front/controller/visitor/InstallControllerTest.php
Normal file
264
tests/front/controller/visitor/InstallControllerTest.php
Normal file
|
@ -0,0 +1,264 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shaarli\Front\Controller\Visitor;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\Front\Exception\AlreadyInstalledException;
|
||||
use Shaarli\Security\SessionManager;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class InstallControllerTest extends TestCase
|
||||
{
|
||||
use FrontControllerMockHelper;
|
||||
|
||||
const MOCK_FILE = '.tmp';
|
||||
|
||||
/** @var InstallController */
|
||||
protected $controller;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->createContainer();
|
||||
|
||||
$this->container->conf = $this->createMock(ConfigManager::class);
|
||||
$this->container->conf->method('getConfigFileExt')->willReturn(static::MOCK_FILE);
|
||||
$this->container->conf->method('get')->willReturnCallback(function (string $key, $default) {
|
||||
if ($key === 'resource.raintpl_tpl') {
|
||||
return '.';
|
||||
}
|
||||
|
||||
return $default ?? $key;
|
||||
});
|
||||
|
||||
$this->controller = new InstallController($this->container);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (file_exists(static::MOCK_FILE)) {
|
||||
unlink(static::MOCK_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test displaying install page with valid session.
|
||||
*/
|
||||
public function testInstallIndexWithValidSession(): void
|
||||
{
|
||||
$assignedVariables = [];
|
||||
$this->assignTemplateVars($assignedVariables);
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->container->sessionManager
|
||||
->method('getSessionParameter')
|
||||
->willReturnCallback(function (string $key, $default) {
|
||||
return $key === 'session_tested' ? 'Working' : $default;
|
||||
})
|
||||
;
|
||||
|
||||
$result = $this->controller->index($request, $response);
|
||||
|
||||
static::assertSame(200, $result->getStatusCode());
|
||||
static::assertSame('install', (string) $result->getBody());
|
||||
|
||||
static::assertIsArray($assignedVariables['continents']);
|
||||
static::assertSame('Africa', $assignedVariables['continents'][0]);
|
||||
static::assertSame('UTC', $assignedVariables['continents']['selected']);
|
||||
|
||||
static::assertIsArray($assignedVariables['cities']);
|
||||
static::assertSame(['continent' => 'Africa', 'city' => 'Abidjan'], $assignedVariables['cities'][0]);
|
||||
static::assertSame('UTC', $assignedVariables['continents']['selected']);
|
||||
|
||||
static::assertIsArray($assignedVariables['languages']);
|
||||
static::assertSame('Automatic', $assignedVariables['languages']['auto']);
|
||||
static::assertSame('French', $assignedVariables['languages']['fr']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate the install controller with an existing config file: exception.
|
||||
*/
|
||||
public function testInstallWithExistingConfigFile(): void
|
||||
{
|
||||
$this->expectException(AlreadyInstalledException::class);
|
||||
|
||||
touch(static::MOCK_FILE);
|
||||
|
||||
$this->controller = new InstallController($this->container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call controller without session yet defined, redirect to test session install page.
|
||||
*/
|
||||
public function testInstallRedirectToSessionTest(): void
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->container->sessionManager
|
||||
->expects(static::once())
|
||||
->method('setSessionParameter')
|
||||
->with(InstallController::SESSION_TEST_KEY, InstallController::SESSION_TEST_VALUE)
|
||||
;
|
||||
|
||||
$result = $this->controller->index($request, $response);
|
||||
|
||||
static::assertSame(302, $result->getStatusCode());
|
||||
static::assertSame('/subfolder/install/session-test', $result->getHeader('location')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call controller in session test mode: valid session then redirect to install page.
|
||||
*/
|
||||
public function testInstallSessionTestValid(): void
|
||||
{
|
||||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->container->sessionManager
|
||||
->method('getSessionParameter')
|
||||
->with(InstallController::SESSION_TEST_KEY)
|
||||
->willReturn(InstallController::SESSION_TEST_VALUE)
|
||||
;
|
||||
|
||||
$result = $this->controller->sessionTest($request, $response);
|
||||
|
||||
static::assertSame(302, $result->getStatusCode());
|
||||
static::assertSame('/subfolder/install', $result->getHeader('location')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call controller in session test mode: invalid session then redirect to error page.
|
||||
*/
|
||||
public function testInstallSessionTestError(): void
|
||||
{
|
||||
$assignedVars = [];
|
||||
$this->assignTemplateVars($assignedVars);
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->sessionManager = $this->createMock(SessionManager::class);
|
||||
$this->container->sessionManager
|
||||
->method('getSessionParameter')
|
||||
->with(InstallController::SESSION_TEST_KEY)
|
||||
->willReturn('KO')
|
||||
;
|
||||
|
||||
$result = $this->controller->sessionTest($request, $response);
|
||||
|
||||
static::assertSame(200, $result->getStatusCode());
|
||||
static::assertSame('error', (string) $result->getBody());
|
||||
static::assertStringStartsWith(
|
||||
'<pre>Sessions do not seem to work correctly on your server',
|
||||
$assignedVars['message']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test saving valid data from install form. Also initialize datastore.
|
||||
*/
|
||||
public function testSaveInstallValid(): void
|
||||
{
|
||||
$providedParameters = [
|
||||
'continent' => 'Europe',
|
||||
'city' => 'Berlin',
|
||||
'setlogin' => 'bob',
|
||||
'setpassword' => 'password',
|
||||
'title' => 'Shaarli',
|
||||
'language' => 'fr',
|
||||
'updateCheck' => true,
|
||||
'enableApi' => true,
|
||||
];
|
||||
|
||||
$expectedSettings = [
|
||||
'general.timezone' => 'Europe/Berlin',
|
||||
'credentials.login' => 'bob',
|
||||
'credentials.salt' => '_NOT_EMPTY',
|
||||
'credentials.hash' => '_NOT_EMPTY',
|
||||
'general.title' => 'Shaarli',
|
||||
'translation.language' => 'en',
|
||||
'updates.check_updates' => true,
|
||||
'api.enabled' => true,
|
||||
'api.secret' => '_NOT_EMPTY',
|
||||
];
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$request->method('getParam')->willReturnCallback(function (string $key) use ($providedParameters) {
|
||||
return $providedParameters[$key] ?? null;
|
||||
});
|
||||
$response = new Response();
|
||||
|
||||
$this->container->conf = $this->createMock(ConfigManager::class);
|
||||
$this->container->conf
|
||||
->method('get')
|
||||
->willReturnCallback(function (string $key, $value) {
|
||||
if ($key === 'credentials.login') {
|
||||
return 'bob';
|
||||
} elseif ($key === 'credentials.salt') {
|
||||
return 'salt';
|
||||
}
|
||||
|
||||
return $value;
|
||||
})
|
||||
;
|
||||
$this->container->conf
|
||||
->expects(static::exactly(count($expectedSettings)))
|
||||
->method('set')
|
||||
->willReturnCallback(function (string $key, $value) use ($expectedSettings) {
|
||||
if ($expectedSettings[$key] ?? null === '_NOT_EMPTY') {
|
||||
static::assertNotEmpty($value);
|
||||
} else {
|
||||
static::assertSame($expectedSettings[$key], $value);
|
||||
}
|
||||
})
|
||||
;
|
||||
$this->container->conf->expects(static::once())->method('write');
|
||||
|
||||
$this->container->bookmarkService->expects(static::once())->method('count')->willReturn(0);
|
||||
$this->container->bookmarkService->expects(static::once())->method('initialize');
|
||||
|
||||
$this->container->sessionManager
|
||||
->expects(static::once())
|
||||
->method('setSessionParameter')
|
||||
->with(SessionManager::KEY_SUCCESS_MESSAGES)
|
||||
;
|
||||
|
||||
$result = $this->controller->save($request, $response);
|
||||
|
||||
static::assertSame(302, $result->getStatusCode());
|
||||
static::assertSame('/subfolder/', $result->getHeader('location')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test default settings (timezone and title).
|
||||
* Also check that bookmarks are not initialized if
|
||||
*/
|
||||
public function testSaveInstallDefaultValues(): void
|
||||
{
|
||||
$confSettings = [];
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
$response = new Response();
|
||||
|
||||
$this->container->conf->method('set')->willReturnCallback(function (string $key, $value) use (&$confSettings) {
|
||||
$confSettings[$key] = $value;
|
||||
});
|
||||
|
||||
$result = $this->controller->save($request, $response);
|
||||
|
||||
static::assertSame(302, $result->getStatusCode());
|
||||
static::assertSame('/subfolder/', $result->getHeader('location')[0]);
|
||||
|
||||
static::assertSame('UTC', $confSettings['general.timezone']);
|
||||
static::assertSame('Shared bookmarks on http://shaarli', $confSettings['general.title']);
|
||||
}
|
||||
}
|
|
@ -1,15 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Cache tests
|
||||
*/
|
||||
|
||||
namespace Shaarli\Render;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shaarli\Security\SessionManager;
|
||||
|
||||
// required to access $_SESSION array
|
||||
session_start();
|
||||
|
||||
/**
|
||||
* Unitary tests for cached pages
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
namespace Shaarli\Security;
|
||||
|
||||
require_once 'tests/utils/FakeConfigManager.php';
|
||||
namespace Shaarli\Security;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
@ -58,6 +57,9 @@ class LoginManagerTest extends TestCase
|
|||
/** @var string Salt used by hash functions */
|
||||
protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2';
|
||||
|
||||
/** @var CookieManager */
|
||||
protected $cookieManager;
|
||||
|
||||
/**
|
||||
* Prepare or reset test resources
|
||||
*/
|
||||
|
@ -84,8 +86,12 @@ class LoginManagerTest extends TestCase
|
|||
$this->cookie = [];
|
||||
$this->session = [];
|
||||
|
||||
$this->sessionManager = new SessionManager($this->session, $this->configManager);
|
||||
$this->loginManager = new LoginManager($this->configManager, $this->sessionManager);
|
||||
$this->cookieManager = $this->createMock(CookieManager::class);
|
||||
$this->cookieManager->method('getCookieParameter')->willReturnCallback(function (string $key) {
|
||||
return $this->cookie[$key] ?? null;
|
||||
});
|
||||
$this->sessionManager = new SessionManager($this->session, $this->configManager, 'session_path');
|
||||
$this->loginManager = new LoginManager($this->configManager, $this->sessionManager, $this->cookieManager);
|
||||
$this->server['REMOTE_ADDR'] = $this->ipAddr;
|
||||
}
|
||||
|
||||
|
@ -193,8 +199,8 @@ class LoginManagerTest extends TestCase
|
|||
$configManager = new \FakeConfigManager([
|
||||
'resource.ban_file' => $this->banFile,
|
||||
]);
|
||||
$loginManager = new LoginManager($configManager, null);
|
||||
$loginManager->checkLoginState([], '');
|
||||
$loginManager = new LoginManager($configManager, null, $this->cookieManager);
|
||||
$loginManager->checkLoginState('');
|
||||
|
||||
$this->assertFalse($loginManager->isLoggedIn());
|
||||
}
|
||||
|
@ -210,9 +216,9 @@ class LoginManagerTest extends TestCase
|
|||
'expires_on' => time() + 100,
|
||||
];
|
||||
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
|
||||
$this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = 'nope';
|
||||
$this->cookie[CookieManager::STAY_SIGNED_IN] = 'nope';
|
||||
|
||||
$this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
|
||||
$this->loginManager->checkLoginState($this->clientIpAddress);
|
||||
|
||||
$this->assertTrue($this->loginManager->isLoggedIn());
|
||||
$this->assertTrue(empty($this->session['username']));
|
||||
|
@ -224,9 +230,9 @@ class LoginManagerTest extends TestCase
|
|||
public function testCheckLoginStateStaySignedInWithValidToken()
|
||||
{
|
||||
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
|
||||
$this->cookie[LoginManager::$STAY_SIGNED_IN_COOKIE] = $this->loginManager->getStaySignedInToken();
|
||||
$this->cookie[CookieManager::STAY_SIGNED_IN] = $this->loginManager->getStaySignedInToken();
|
||||
|
||||
$this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
|
||||
$this->loginManager->checkLoginState($this->clientIpAddress);
|
||||
|
||||
$this->assertTrue($this->loginManager->isLoggedIn());
|
||||
$this->assertEquals($this->login, $this->session['username']);
|
||||
|
@ -241,7 +247,7 @@ class LoginManagerTest extends TestCase
|
|||
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
|
||||
$this->session['expires_on'] = time() - 100;
|
||||
|
||||
$this->loginManager->checkLoginState($this->cookie, $this->clientIpAddress);
|
||||
$this->loginManager->checkLoginState($this->clientIpAddress);
|
||||
|
||||
$this->assertFalse($this->loginManager->isLoggedIn());
|
||||
}
|
||||
|
@ -253,7 +259,7 @@ class LoginManagerTest extends TestCase
|
|||
{
|
||||
$this->loginManager->generateStaySignedInToken($this->clientIpAddress);
|
||||
|
||||
$this->loginManager->checkLoginState($this->cookie, '10.7.157.98');
|
||||
$this->loginManager->checkLoginState('10.7.157.98');
|
||||
|
||||
$this->assertFalse($this->loginManager->isLoggedIn());
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
<?php
|
||||
require_once 'tests/utils/FakeConfigManager.php';
|
||||
|
||||
// Initialize reference data _before_ PHPUnit starts a session
|
||||
require_once 'tests/utils/ReferenceSessionIdHashes.php';
|
||||
ReferenceSessionIdHashes::genAllHashes();
|
||||
namespace Shaarli\Security;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shaarli\Security\SessionManager;
|
||||
|
||||
/**
|
||||
* Test coverage for SessionManager
|
||||
|
@ -30,7 +26,7 @@ class SessionManagerTest extends TestCase
|
|||
*/
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$sidHashes = ReferenceSessionIdHashes::getHashes();
|
||||
self::$sidHashes = \ReferenceSessionIdHashes::getHashes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,13 +34,13 @@ class SessionManagerTest extends TestCase
|
|||
*/
|
||||
public function setUp()
|
||||
{
|
||||
$this->conf = new FakeConfigManager([
|
||||
$this->conf = new \FakeConfigManager([
|
||||
'credentials.login' => 'johndoe',
|
||||
'credentials.salt' => 'salt',
|
||||
'security.session_protection_disabled' => false,
|
||||
]);
|
||||
$this->session = [];
|
||||
$this->sessionManager = new SessionManager($this->session, $this->conf);
|
||||
$this->sessionManager = new SessionManager($this->session, $this->conf, 'session_path');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,7 +65,7 @@ class SessionManagerTest extends TestCase
|
|||
$token => 1,
|
||||
],
|
||||
];
|
||||
$sessionManager = new SessionManager($session, $this->conf);
|
||||
$sessionManager = new SessionManager($session, $this->conf, 'session_path');
|
||||
|
||||
// check and destroy the token
|
||||
$this->assertTrue($sessionManager->checkToken($token));
|
||||
|
|
|
@ -7,9 +7,6 @@ use Shaarli\Bookmark\BookmarkServiceInterface;
|
|||
use Shaarli\Config\ConfigManager;
|
||||
use Shaarli\History;
|
||||
|
||||
require_once 'tests/updater/DummyUpdater.php';
|
||||
require_once 'tests/utils/ReferenceLinkDB.php';
|
||||
require_once 'inc/rain.tpl.class.php';
|
||||
|
||||
/**
|
||||
* Class UpdaterTest.
|
||||
|
@ -35,6 +32,9 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase
|
|||
/** @var BookmarkServiceInterface */
|
||||
protected $bookmarkService;
|
||||
|
||||
/** @var \ReferenceLinkDB */
|
||||
protected $refDB;
|
||||
|
||||
/** @var Updater */
|
||||
protected $updater;
|
||||
|
||||
|
@ -43,6 +43,9 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase
|
|||
*/
|
||||
public function setUp()
|
||||
{
|
||||
$this->refDB = new \ReferenceLinkDB();
|
||||
$this->refDB->write(self::$testDatastore);
|
||||
|
||||
copy('tests/utils/config/configJson.json.php', self::$configFile .'.json.php');
|
||||
$this->conf = new ConfigManager(self::$configFile);
|
||||
$this->bookmarkService = new BookmarkFileService($this->conf, $this->createMock(History::class), true);
|
||||
|
@ -181,9 +184,40 @@ class UpdaterTest extends \PHPUnit\Framework\TestCase
|
|||
|
||||
public function testUpdateMethodRelativeHomeLinkRename(): void
|
||||
{
|
||||
$this->updater->setBasePath('/subfolder');
|
||||
$this->conf->set('general.header_link', '?');
|
||||
|
||||
$this->updater->updateMethodRelativeHomeLink();
|
||||
|
||||
static::assertSame();
|
||||
static::assertSame('/subfolder/', $this->conf->get('general.header_link'));
|
||||
}
|
||||
|
||||
public function testUpdateMethodRelativeHomeLinkDoNotRename(): void
|
||||
{
|
||||
$this->updater->setBasePath('/subfolder');
|
||||
$this->conf->set('general.header_link', '~/my-blog');
|
||||
|
||||
$this->updater->updateMethodRelativeHomeLink();
|
||||
|
||||
static::assertSame('~/my-blog', $this->conf->get('general.header_link'));
|
||||
}
|
||||
|
||||
public function testUpdateMethodMigrateExistingNotesUrl(): void
|
||||
{
|
||||
$this->updater->setBasePath('/subfolder');
|
||||
|
||||
$this->updater->updateMethodMigrateExistingNotesUrl();
|
||||
|
||||
static::assertSame($this->refDB->getLinks()[0]->getUrl(), $this->bookmarkService->get(0)->getUrl());
|
||||
static::assertSame($this->refDB->getLinks()[1]->getUrl(), $this->bookmarkService->get(1)->getUrl());
|
||||
static::assertSame($this->refDB->getLinks()[4]->getUrl(), $this->bookmarkService->get(4)->getUrl());
|
||||
static::assertSame($this->refDB->getLinks()[6]->getUrl(), $this->bookmarkService->get(6)->getUrl());
|
||||
static::assertSame($this->refDB->getLinks()[7]->getUrl(), $this->bookmarkService->get(7)->getUrl());
|
||||
static::assertSame($this->refDB->getLinks()[8]->getUrl(), $this->bookmarkService->get(8)->getUrl());
|
||||
static::assertSame($this->refDB->getLinks()[9]->getUrl(), $this->bookmarkService->get(9)->getUrl());
|
||||
static::assertSame('/subfolder/shaare/WDWyig', $this->bookmarkService->get(42)->getUrl());
|
||||
static::assertSame('/subfolder/shaare/WDWyig', $this->bookmarkService->get(41)->getUrl());
|
||||
static::assertSame('/subfolder/shaare/0gCTjQ', $this->bookmarkService->get(10)->getUrl());
|
||||
static::assertSame('/subfolder/shaare/PCRizQ', $this->bookmarkService->get(11)->getUrl());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</pre>
|
||||
{/if}
|
||||
|
||||
<img src="{asset_path}/img/sad_star.png#" alt="">
|
||||
<img src="{$asset_path}/img/sad_star.png#" alt="">
|
||||
</div>
|
||||
{include="page.footer"}
|
||||
</body>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{$ratioLabelMobile='7-8'}
|
||||
{$ratioInputMobile='1-8'}
|
||||
|
||||
<form method="POST" action="{$base_path}/?do=install" name="installform" id="installform">
|
||||
<form method="POST" action="{$base_path}/install" name="installform" id="installform">
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-lg-1-6 pure-u-1-24"></div>
|
||||
<div class="pure-u-lg-2-3 pure-u-22-24 page-form page-form-complete">
|
||||
|
|
|
@ -184,7 +184,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{if="!empty($global_errors) && $is_logged_in"}
|
||||
{if="!empty($global_errors)"}
|
||||
<div class="pure-g header-alert-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">
|
||||
|
@ -198,7 +198,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{if="!empty($global_warnings) && $is_logged_in"}
|
||||
{if="!empty($global_warnings)"}
|
||||
<div class="pure-g header-alert-message pure-alert pure-alert-warning pure-alert-closable" id="shaarli-warnings-alert">
|
||||
<div class="pure-u-2-24"></div>
|
||||
<div class="pure-u-20-24">
|
||||
|
@ -212,7 +212,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{if="!empty($global_successes) && $is_logged_in"}
|
||||
{if="!empty($global_successes)"}
|
||||
<div class="pure-g header-alert-message 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">
|
||||
|
|
Loading…
Reference in a new issue