diff options
Diffstat (limited to 'application')
-rw-r--r-- | application/bookmark/BookmarkFileService.php | 26 | ||||
-rw-r--r-- | application/bookmark/BookmarkInitializer.php | 12 | ||||
-rw-r--r-- | application/bookmark/BookmarkServiceInterface.php | 13 | ||||
-rw-r--r-- | application/container/ContainerBuilder.php | 7 | ||||
-rw-r--r-- | application/container/ShaarliContainer.php | 3 | ||||
-rw-r--r-- | application/front/ShaarliMiddleware.php | 6 | ||||
-rw-r--r-- | application/front/controller/admin/LogoutController.php | 10 | ||||
-rw-r--r-- | application/front/controller/visitor/InstallController.php | 173 | ||||
-rw-r--r-- | application/front/exceptions/AlreadyInstalledException.php | 15 | ||||
-rw-r--r-- | application/front/exceptions/ResourcePermissionException.php | 13 | ||||
-rw-r--r-- | application/security/CookieManager.php | 33 | ||||
-rw-r--r-- | application/security/LoginManager.php | 16 | ||||
-rw-r--r-- | application/security/SessionManager.php | 16 | ||||
-rw-r--r-- | application/updater/Updater.php | 30 |
14 files changed, 340 insertions, 33 deletions
diff --git a/application/bookmark/BookmarkFileService.php b/application/bookmark/BookmarkFileService.php index 3d15d4c9..6e04f3b7 100644 --- a/application/bookmark/BookmarkFileService.php +++ b/application/bookmark/BookmarkFileService.php | |||
@@ -46,6 +46,9 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
46 | /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ | 46 | /** @var bool true for logged in users. Default value to retrieve private bookmarks. */ |
47 | protected $isLoggedIn; | 47 | protected $isLoggedIn; |
48 | 48 | ||
49 | /** @var bool Allow datastore alteration from not logged in users. */ | ||
50 | protected $anonymousPermission = false; | ||
51 | |||
49 | /** | 52 | /** |
50 | * @inheritDoc | 53 | * @inheritDoc |
51 | */ | 54 | */ |
@@ -64,7 +67,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
64 | $this->bookmarks = $this->bookmarksIO->read(); | 67 | $this->bookmarks = $this->bookmarksIO->read(); |
65 | } catch (EmptyDataStoreException $e) { | 68 | } catch (EmptyDataStoreException $e) { |
66 | $this->bookmarks = new BookmarkArray(); | 69 | $this->bookmarks = new BookmarkArray(); |
67 | if ($isLoggedIn) { | 70 | if ($this->isLoggedIn) { |
68 | $this->save(); | 71 | $this->save(); |
69 | } | 72 | } |
70 | } | 73 | } |
@@ -154,7 +157,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
154 | */ | 157 | */ |
155 | public function set($bookmark, $save = true) | 158 | public function set($bookmark, $save = true) |
156 | { | 159 | { |
157 | if ($this->isLoggedIn !== true) { | 160 | if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) { |
158 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 161 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
159 | } | 162 | } |
160 | if (! $bookmark instanceof Bookmark) { | 163 | if (! $bookmark instanceof Bookmark) { |
@@ -179,7 +182,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
179 | */ | 182 | */ |
180 | public function add($bookmark, $save = true) | 183 | public function add($bookmark, $save = true) |
181 | { | 184 | { |
182 | if ($this->isLoggedIn !== true) { | 185 | if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) { |
183 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 186 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
184 | } | 187 | } |
185 | if (! $bookmark instanceof Bookmark) { | 188 | if (! $bookmark instanceof Bookmark) { |
@@ -204,7 +207,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
204 | */ | 207 | */ |
205 | public function addOrSet($bookmark, $save = true) | 208 | public function addOrSet($bookmark, $save = true) |
206 | { | 209 | { |
207 | if ($this->isLoggedIn !== true) { | 210 | if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) { |
208 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 211 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
209 | } | 212 | } |
210 | if (! $bookmark instanceof Bookmark) { | 213 | if (! $bookmark instanceof Bookmark) { |
@@ -221,7 +224,7 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
221 | */ | 224 | */ |
222 | public function remove($bookmark, $save = true) | 225 | public function remove($bookmark, $save = true) |
223 | { | 226 | { |
224 | if ($this->isLoggedIn !== true) { | 227 | if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) { |
225 | throw new Exception(t('You\'re not authorized to alter the datastore')); | 228 | throw new Exception(t('You\'re not authorized to alter the datastore')); |
226 | } | 229 | } |
227 | if (! $bookmark instanceof Bookmark) { | 230 | if (! $bookmark instanceof Bookmark) { |
@@ -274,10 +277,11 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
274 | */ | 277 | */ |
275 | public function save() | 278 | public function save() |
276 | { | 279 | { |
277 | if (!$this->isLoggedIn) { | 280 | if (true !== $this->isLoggedIn && true !== $this->anonymousPermission) { |
278 | // TODO: raise an Exception instead | 281 | // TODO: raise an Exception instead |
279 | die('You are not authorized to change the database.'); | 282 | die('You are not authorized to change the database.'); |
280 | } | 283 | } |
284 | |||
281 | $this->bookmarks->reorder(); | 285 | $this->bookmarks->reorder(); |
282 | $this->bookmarksIO->write($this->bookmarks); | 286 | $this->bookmarksIO->write($this->bookmarks); |
283 | $this->pageCacheManager->invalidateCaches(); | 287 | $this->pageCacheManager->invalidateCaches(); |
@@ -357,6 +361,16 @@ class BookmarkFileService implements BookmarkServiceInterface | |||
357 | $initializer->initialize(); | 361 | $initializer->initialize(); |
358 | } | 362 | } |
359 | 363 | ||
364 | public function enableAnonymousPermission(): void | ||
365 | { | ||
366 | $this->anonymousPermission = true; | ||
367 | } | ||
368 | |||
369 | public function disableAnonymousPermission(): void | ||
370 | { | ||
371 | $this->anonymousPermission = false; | ||
372 | } | ||
373 | |||
360 | /** | 374 | /** |
361 | * Handles migration to the new database format (BookmarksArray). | 375 | * Handles migration to the new database format (BookmarksArray). |
362 | */ | 376 | */ |
diff --git a/application/bookmark/BookmarkInitializer.php b/application/bookmark/BookmarkInitializer.php index 9eee9a35..479ee9a9 100644 --- a/application/bookmark/BookmarkInitializer.php +++ b/application/bookmark/BookmarkInitializer.php | |||
@@ -34,13 +34,15 @@ class BookmarkInitializer | |||
34 | */ | 34 | */ |
35 | public function initialize() | 35 | public function initialize() |
36 | { | 36 | { |
37 | $this->bookmarkService->enableAnonymousPermission(); | ||
38 | |||
37 | $bookmark = new Bookmark(); | 39 | $bookmark = new Bookmark(); |
38 | $bookmark->setTitle(t('My secret stuff... - Pastebin.com')); | 40 | $bookmark->setTitle(t('My secret stuff... - Pastebin.com')); |
39 | $bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', []); | 41 | $bookmark->setUrl('http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8='); |
40 | $bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.')); | 42 | $bookmark->setDescription(t('Shhhh! I\'m a private link only YOU can see. You can delete me too.')); |
41 | $bookmark->setTagsString('secretstuff'); | 43 | $bookmark->setTagsString('secretstuff'); |
42 | $bookmark->setPrivate(true); | 44 | $bookmark->setPrivate(true); |
43 | $this->bookmarkService->add($bookmark); | 45 | $this->bookmarkService->add($bookmark, false); |
44 | 46 | ||
45 | $bookmark = new Bookmark(); | 47 | $bookmark = new Bookmark(); |
46 | $bookmark->setTitle(t('The personal, minimalist, super-fast, database free, bookmarking service')); | 48 | $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 | |||
54 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.' | 56 | You use the community supported version of the original Shaarli project, by Sebastien Sauvage.' |
55 | )); | 57 | )); |
56 | $bookmark->setTagsString('opensource software'); | 58 | $bookmark->setTagsString('opensource software'); |
57 | $this->bookmarkService->add($bookmark); | 59 | $this->bookmarkService->add($bookmark, false); |
60 | |||
61 | $this->bookmarkService->save(); | ||
62 | |||
63 | $this->bookmarkService->disableAnonymousPermission(); | ||
58 | } | 64 | } |
59 | } | 65 | } |
diff --git a/application/bookmark/BookmarkServiceInterface.php b/application/bookmark/BookmarkServiceInterface.php index 7b7a4f09..37fbda89 100644 --- a/application/bookmark/BookmarkServiceInterface.php +++ b/application/bookmark/BookmarkServiceInterface.php | |||
@@ -177,4 +177,17 @@ interface BookmarkServiceInterface | |||
177 | * Creates the default database after a fresh install. | 177 | * Creates the default database after a fresh install. |
178 | */ | 178 | */ |
179 | public function initialize(); | 179 | public function initialize(); |
180 | |||
181 | /** | ||
182 | * Allow to write the datastore from anonymous session (not logged in). | ||
183 | * | ||
184 | * This covers a few specific use cases, such as datastore initialization, | ||
185 | * but it should be used carefully as it can lead to security issues. | ||
186 | */ | ||
187 | public function enableAnonymousPermission(); | ||
188 | |||
189 | /** | ||
190 | * Disable anonymous permission. | ||
191 | */ | ||
192 | public function disableAnonymousPermission(); | ||
180 | } | 193 | } |
diff --git a/application/container/ContainerBuilder.php b/application/container/ContainerBuilder.php index ccb87c3a..593aafb7 100644 --- a/application/container/ContainerBuilder.php +++ b/application/container/ContainerBuilder.php | |||
@@ -15,6 +15,7 @@ use Shaarli\Netscape\NetscapeBookmarkUtils; | |||
15 | use Shaarli\Plugin\PluginManager; | 15 | use Shaarli\Plugin\PluginManager; |
16 | use Shaarli\Render\PageBuilder; | 16 | use Shaarli\Render\PageBuilder; |
17 | use Shaarli\Render\PageCacheManager; | 17 | use Shaarli\Render\PageCacheManager; |
18 | use Shaarli\Security\CookieManager; | ||
18 | use Shaarli\Security\LoginManager; | 19 | use Shaarli\Security\LoginManager; |
19 | use Shaarli\Security\SessionManager; | 20 | use Shaarli\Security\SessionManager; |
20 | use Shaarli\Thumbnailer; | 21 | use Shaarli\Thumbnailer; |
@@ -38,6 +39,9 @@ class ContainerBuilder | |||
38 | /** @var SessionManager */ | 39 | /** @var SessionManager */ |
39 | protected $session; | 40 | protected $session; |
40 | 41 | ||
42 | /** @var CookieManager */ | ||
43 | protected $cookieManager; | ||
44 | |||
41 | /** @var LoginManager */ | 45 | /** @var LoginManager */ |
42 | protected $login; | 46 | protected $login; |
43 | 47 | ||
@@ -47,11 +51,13 @@ class ContainerBuilder | |||
47 | public function __construct( | 51 | public function __construct( |
48 | ConfigManager $conf, | 52 | ConfigManager $conf, |
49 | SessionManager $session, | 53 | SessionManager $session, |
54 | CookieManager $cookieManager, | ||
50 | LoginManager $login | 55 | LoginManager $login |
51 | ) { | 56 | ) { |
52 | $this->conf = $conf; | 57 | $this->conf = $conf; |
53 | $this->session = $session; | 58 | $this->session = $session; |
54 | $this->login = $login; | 59 | $this->login = $login; |
60 | $this->cookieManager = $cookieManager; | ||
55 | } | 61 | } |
56 | 62 | ||
57 | public function build(): ShaarliContainer | 63 | public function build(): ShaarliContainer |
@@ -60,6 +66,7 @@ class ContainerBuilder | |||
60 | 66 | ||
61 | $container['conf'] = $this->conf; | 67 | $container['conf'] = $this->conf; |
62 | $container['sessionManager'] = $this->session; | 68 | $container['sessionManager'] = $this->session; |
69 | $container['cookieManager'] = $this->cookieManager; | ||
63 | $container['loginManager'] = $this->login; | 70 | $container['loginManager'] = $this->login; |
64 | $container['basePath'] = $this->basePath; | 71 | $container['basePath'] = $this->basePath; |
65 | 72 | ||
diff --git a/application/container/ShaarliContainer.php b/application/container/ShaarliContainer.php index 09e7d5b1..c4fe753e 100644 --- a/application/container/ShaarliContainer.php +++ b/application/container/ShaarliContainer.php | |||
@@ -4,6 +4,7 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Container; | 5 | namespace Shaarli\Container; |
6 | 6 | ||
7 | use http\Cookie; | ||
7 | use Shaarli\Bookmark\BookmarkServiceInterface; | 8 | use Shaarli\Bookmark\BookmarkServiceInterface; |
8 | use Shaarli\Config\ConfigManager; | 9 | use Shaarli\Config\ConfigManager; |
9 | use Shaarli\Feed\FeedBuilder; | 10 | use Shaarli\Feed\FeedBuilder; |
@@ -14,6 +15,7 @@ use Shaarli\Netscape\NetscapeBookmarkUtils; | |||
14 | use Shaarli\Plugin\PluginManager; | 15 | use Shaarli\Plugin\PluginManager; |
15 | use Shaarli\Render\PageBuilder; | 16 | use Shaarli\Render\PageBuilder; |
16 | use Shaarli\Render\PageCacheManager; | 17 | use Shaarli\Render\PageCacheManager; |
18 | use Shaarli\Security\CookieManager; | ||
17 | use Shaarli\Security\LoginManager; | 19 | use Shaarli\Security\LoginManager; |
18 | use Shaarli\Security\SessionManager; | 20 | use Shaarli\Security\SessionManager; |
19 | use Shaarli\Thumbnailer; | 21 | use Shaarli\Thumbnailer; |
@@ -25,6 +27,7 @@ use Slim\Container; | |||
25 | * | 27 | * |
26 | * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) | 28 | * @property string $basePath Shaarli's instance base path (e.g. `/shaarli/`) |
27 | * @property BookmarkServiceInterface $bookmarkService | 29 | * @property BookmarkServiceInterface $bookmarkService |
30 | * @property CookieManager $cookieManager | ||
28 | * @property ConfigManager $conf | 31 | * @property ConfigManager $conf |
29 | * @property mixed[] $environment $_SERVER automatically injected by Slim | 32 | * @property mixed[] $environment $_SERVER automatically injected by Slim |
30 | * @property callable $errorHandler Overrides default Slim error display | 33 | * @property callable $errorHandler Overrides default Slim error display |
diff --git a/application/front/ShaarliMiddleware.php b/application/front/ShaarliMiddleware.php index baea6ef2..595182ac 100644 --- a/application/front/ShaarliMiddleware.php +++ b/application/front/ShaarliMiddleware.php | |||
@@ -43,6 +43,12 @@ class ShaarliMiddleware | |||
43 | $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); | 43 | $this->container->basePath = rtrim($request->getUri()->getBasePath(), '/'); |
44 | 44 | ||
45 | try { | 45 | try { |
46 | if (!is_file($this->container->conf->getConfigFileExt()) | ||
47 | && !in_array($next->getName(), ['displayInstall', 'saveInstall'], true) | ||
48 | ) { | ||
49 | return $response->withRedirect($this->container->basePath . '/install'); | ||
50 | } | ||
51 | |||
46 | $this->runUpdates(); | 52 | $this->runUpdates(); |
47 | $this->checkOpenShaarli($request, $response, $next); | 53 | $this->checkOpenShaarli($request, $response, $next); |
48 | 54 | ||
diff --git a/application/front/controller/admin/LogoutController.php b/application/front/controller/admin/LogoutController.php index c5984814..28165129 100644 --- a/application/front/controller/admin/LogoutController.php +++ b/application/front/controller/admin/LogoutController.php | |||
@@ -4,6 +4,7 @@ declare(strict_types=1); | |||
4 | 4 | ||
5 | namespace Shaarli\Front\Controller\Admin; | 5 | namespace Shaarli\Front\Controller\Admin; |
6 | 6 | ||
7 | use Shaarli\Security\CookieManager; | ||
7 | use Shaarli\Security\LoginManager; | 8 | use Shaarli\Security\LoginManager; |
8 | use Slim\Http\Request; | 9 | use Slim\Http\Request; |
9 | use Slim\Http\Response; | 10 | use Slim\Http\Response; |
@@ -20,9 +21,12 @@ class LogoutController extends ShaarliAdminController | |||
20 | { | 21 | { |
21 | $this->container->pageCacheManager->invalidateCaches(); | 22 | $this->container->pageCacheManager->invalidateCaches(); |
22 | $this->container->sessionManager->logout(); | 23 | $this->container->sessionManager->logout(); |
23 | 24 | $this->container->cookieManager->setCookieParameter( | |
24 | // TODO: switch to a simple Cookie manager allowing to check the session, and create mocks. | 25 | CookieManager::STAY_SIGNED_IN, |
25 | setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, $this->container->basePath . '/'); | 26 | 'false', |
27 | 0, | ||
28 | $this->container->basePath . '/' | ||
29 | ); | ||
26 | 30 | ||
27 | return $this->redirect($response, '/'); | 31 | return $this->redirect($response, '/'); |
28 | } | 32 | } |
diff --git a/application/front/controller/visitor/InstallController.php b/application/front/controller/visitor/InstallController.php new file mode 100644 index 00000000..aa032860 --- /dev/null +++ b/application/front/controller/visitor/InstallController.php | |||
@@ -0,0 +1,173 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
6 | |||
7 | use Shaarli\ApplicationUtils; | ||
8 | use Shaarli\Bookmark\BookmarkFilter; | ||
9 | use Shaarli\Container\ShaarliContainer; | ||
10 | use Shaarli\Front\Exception\AlreadyInstalledException; | ||
11 | use Shaarli\Front\Exception\ResourcePermissionException; | ||
12 | use Shaarli\Languages; | ||
13 | use Shaarli\Security\SessionManager; | ||
14 | use Slim\Http\Request; | ||
15 | use Slim\Http\Response; | ||
16 | |||
17 | /** | ||
18 | * Slim controller used to render install page, and create initial configuration file. | ||
19 | */ | ||
20 | class InstallController extends ShaarliVisitorController | ||
21 | { | ||
22 | public const SESSION_TEST_KEY = 'session_tested'; | ||
23 | public const SESSION_TEST_VALUE = 'Working'; | ||
24 | |||
25 | public function __construct(ShaarliContainer $container) | ||
26 | { | ||
27 | parent::__construct($container); | ||
28 | |||
29 | if (is_file($this->container->conf->getConfigFileExt())) { | ||
30 | throw new AlreadyInstalledException(); | ||
31 | } | ||
32 | } | ||
33 | |||
34 | /** | ||
35 | * Display the install template page. | ||
36 | * Also test file permissions and sessions beforehand. | ||
37 | */ | ||
38 | public function index(Request $request, Response $response): Response | ||
39 | { | ||
40 | // Before installation, we'll make sure that permissions are set properly, and sessions are working. | ||
41 | $this->checkPermissions(); | ||
42 | |||
43 | if (static::SESSION_TEST_VALUE | ||
44 | !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) | ||
45 | ) { | ||
46 | $this->container->sessionManager->setSessionParameter(static::SESSION_TEST_KEY, static::SESSION_TEST_VALUE); | ||
47 | |||
48 | return $this->redirect($response, '/install/session-test'); | ||
49 | } | ||
50 | |||
51 | [$continents, $cities] = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); | ||
52 | |||
53 | $this->assignView('continents', $continents); | ||
54 | $this->assignView('cities', $cities); | ||
55 | $this->assignView('languages', Languages::getAvailableLanguages()); | ||
56 | |||
57 | return $response->write($this->render('install')); | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Route checking that the session parameter has been properly saved between two distinct requests. | ||
62 | * If the session parameter is preserved, redirect to install template page, otherwise displays error. | ||
63 | */ | ||
64 | public function sessionTest(Request $request, Response $response): Response | ||
65 | { | ||
66 | // This part makes sure sessions works correctly. | ||
67 | // (Because on some hosts, session.save_path may not be set correctly, | ||
68 | // or we may not have write access to it.) | ||
69 | if (static::SESSION_TEST_VALUE | ||
70 | !== $this->container->sessionManager->getSessionParameter(static::SESSION_TEST_KEY) | ||
71 | ) { | ||
72 | // Step 2: Check if data in session is correct. | ||
73 | $msg = t( | ||
74 | '<pre>Sessions do not seem to work correctly on your server.<br>'. | ||
75 | 'Make sure the variable "session.save_path" is set correctly in your PHP config, '. | ||
76 | 'and that you have write access to it.<br>'. | ||
77 | 'It currently points to %s.<br>'. | ||
78 | 'On some browsers, accessing your server via a hostname like \'localhost\' '. | ||
79 | 'or any custom hostname without a dot causes cookie storage to fail. '. | ||
80 | 'We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>' | ||
81 | ); | ||
82 | $msg = sprintf($msg, $this->container->sessionManager->getSavePath()); | ||
83 | |||
84 | $this->assignView('message', $msg); | ||
85 | |||
86 | return $response->write($this->render('error')); | ||
87 | } | ||
88 | |||
89 | return $this->redirect($response, '/install'); | ||
90 | } | ||
91 | |||
92 | /** | ||
93 | * Save installation form and initialize config file and datastore if necessary. | ||
94 | */ | ||
95 | public function save(Request $request, Response $response): Response | ||
96 | { | ||
97 | $timezone = 'UTC'; | ||
98 | if (!empty($request->getParam('continent')) | ||
99 | && !empty($request->getParam('city')) | ||
100 | && isTimeZoneValid($request->getParam('continent'), $request->getParam('city')) | ||
101 | ) { | ||
102 | $timezone = $request->getParam('continent') . '/' . $request->getParam('city'); | ||
103 | } | ||
104 | $this->container->conf->set('general.timezone', $timezone); | ||
105 | |||
106 | $login = $request->getParam('setlogin'); | ||
107 | $this->container->conf->set('credentials.login', $login); | ||
108 | $salt = sha1(uniqid('', true) .'_'. mt_rand()); | ||
109 | $this->container->conf->set('credentials.salt', $salt); | ||
110 | $this->container->conf->set('credentials.hash', sha1($request->getParam('setpassword') . $login . $salt)); | ||
111 | |||
112 | if (!empty($request->getParam('title'))) { | ||
113 | $this->container->conf->set('general.title', escape($request->getParam('title'))); | ||
114 | } else { | ||
115 | $this->container->conf->set( | ||
116 | 'general.title', | ||
117 | 'Shared bookmarks on '.escape(index_url($this->container->environment)) | ||
118 | ); | ||
119 | } | ||
120 | |||
121 | $this->container->conf->set('translation.language', escape($request->getParam('language'))); | ||
122 | $this->container->conf->set('updates.check_updates', !empty($request->getParam('updateCheck'))); | ||
123 | $this->container->conf->set('api.enabled', !empty($request->getParam('enableApi'))); | ||
124 | $this->container->conf->set( | ||
125 | 'api.secret', | ||
126 | generate_api_secret( | ||
127 | $this->container->conf->get('credentials.login'), | ||
128 | $this->container->conf->get('credentials.salt') | ||
129 | ) | ||
130 | ); | ||
131 | |||
132 | try { | ||
133 | // Everything is ok, let's create config file. | ||
134 | $this->container->conf->write($this->container->loginManager->isLoggedIn()); | ||
135 | } catch (\Exception $e) { | ||
136 | $this->assignView('message', $e->getMessage()); | ||
137 | $this->assignView('stacktrace', $e->getTraceAsString()); | ||
138 | |||
139 | return $response->write($this->render('error')); | ||
140 | } | ||
141 | |||
142 | if ($this->container->bookmarkService->count(BookmarkFilter::$ALL) === 0) { | ||
143 | $this->container->bookmarkService->initialize(); | ||
144 | } | ||
145 | |||
146 | $this->container->sessionManager->setSessionParameter( | ||
147 | SessionManager::KEY_SUCCESS_MESSAGES, | ||
148 | [t('Shaarli is now configured. Please login and start shaaring your bookmarks!')] | ||
149 | ); | ||
150 | |||
151 | return $this->redirect($response, '/'); | ||
152 | } | ||
153 | |||
154 | protected function checkPermissions(): bool | ||
155 | { | ||
156 | // Ensure Shaarli has proper access to its resources | ||
157 | $errors = ApplicationUtils::checkResourcePermissions($this->container->conf); | ||
158 | |||
159 | if (empty($errors)) { | ||
160 | return true; | ||
161 | } | ||
162 | |||
163 | // FIXME! Do not insert HTML here. | ||
164 | $message = '<p>'. t('Insufficient permissions:') .'</p><ul>'; | ||
165 | |||
166 | foreach ($errors as $error) { | ||
167 | $message .= '<li>'.$error.'</li>'; | ||
168 | } | ||
169 | $message .= '</ul>'; | ||
170 | |||
171 | throw new ResourcePermissionException($message); | ||
172 | } | ||
173 | } | ||
diff --git a/application/front/exceptions/AlreadyInstalledException.php b/application/front/exceptions/AlreadyInstalledException.php new file mode 100644 index 00000000..4add86cf --- /dev/null +++ b/application/front/exceptions/AlreadyInstalledException.php | |||
@@ -0,0 +1,15 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class AlreadyInstalledException extends ShaarliFrontException | ||
8 | { | ||
9 | public function __construct() | ||
10 | { | ||
11 | $message = t('Shaarli has already been installed. Login to edit the configuration.'); | ||
12 | |||
13 | parent::__construct($message, 401); | ||
14 | } | ||
15 | } | ||
diff --git a/application/front/exceptions/ResourcePermissionException.php b/application/front/exceptions/ResourcePermissionException.php new file mode 100644 index 00000000..8fbf03b9 --- /dev/null +++ b/application/front/exceptions/ResourcePermissionException.php | |||
@@ -0,0 +1,13 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Exception; | ||
6 | |||
7 | class ResourcePermissionException extends ShaarliFrontException | ||
8 | { | ||
9 | public function __construct(string $message) | ||
10 | { | ||
11 | parent::__construct($message, 500); | ||
12 | } | ||
13 | } | ||
diff --git a/application/security/CookieManager.php b/application/security/CookieManager.php new file mode 100644 index 00000000..cde4746e --- /dev/null +++ b/application/security/CookieManager.php | |||
@@ -0,0 +1,33 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Security; | ||
6 | |||
7 | class CookieManager | ||
8 | { | ||
9 | /** @var string Name of the cookie set after logging in **/ | ||
10 | public const STAY_SIGNED_IN = 'shaarli_staySignedIn'; | ||
11 | |||
12 | /** @var mixed $_COOKIE set by reference */ | ||
13 | protected $cookies; | ||
14 | |||
15 | public function __construct(array &$cookies) | ||
16 | { | ||
17 | $this->cookies = $cookies; | ||
18 | } | ||
19 | |||
20 | public function setCookieParameter(string $key, string $value, int $expires, string $path): self | ||
21 | { | ||
22 | $this->cookies[$key] = $value; | ||
23 | |||
24 | setcookie($key, $value, $expires, $path); | ||
25 | |||
26 | return $this; | ||
27 | } | ||
28 | |||
29 | public function getCookieParameter(string $key, string $default = null): ?string | ||
30 | { | ||
31 | return $this->cookies[$key] ?? $default; | ||
32 | } | ||
33 | } | ||
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php index 39ec9b2e..d74c3118 100644 --- a/application/security/LoginManager.php +++ b/application/security/LoginManager.php | |||
@@ -9,9 +9,6 @@ use Shaarli\Config\ConfigManager; | |||
9 | */ | 9 | */ |
10 | class LoginManager | 10 | class LoginManager |
11 | { | 11 | { |
12 | /** @var string Name of the cookie set after logging in **/ | ||
13 | public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn'; | ||
14 | |||
15 | /** @var array A reference to the $_GLOBALS array */ | 12 | /** @var array A reference to the $_GLOBALS array */ |
16 | protected $globals = []; | 13 | protected $globals = []; |
17 | 14 | ||
@@ -32,17 +29,21 @@ class LoginManager | |||
32 | 29 | ||
33 | /** @var string User sign-in token depending on remote IP and credentials */ | 30 | /** @var string User sign-in token depending on remote IP and credentials */ |
34 | protected $staySignedInToken = ''; | 31 | protected $staySignedInToken = ''; |
32 | /** @var CookieManager */ | ||
33 | protected $cookieManager; | ||
35 | 34 | ||
36 | /** | 35 | /** |
37 | * Constructor | 36 | * Constructor |
38 | * | 37 | * |
39 | * @param ConfigManager $configManager Configuration Manager instance | 38 | * @param ConfigManager $configManager Configuration Manager instance |
40 | * @param SessionManager $sessionManager SessionManager instance | 39 | * @param SessionManager $sessionManager SessionManager instance |
40 | * @param CookieManager $cookieManager CookieManager instance | ||
41 | */ | 41 | */ |
42 | public function __construct($configManager, $sessionManager) | 42 | public function __construct($configManager, $sessionManager, $cookieManager) |
43 | { | 43 | { |
44 | $this->configManager = $configManager; | 44 | $this->configManager = $configManager; |
45 | $this->sessionManager = $sessionManager; | 45 | $this->sessionManager = $sessionManager; |
46 | $this->cookieManager = $cookieManager; | ||
46 | $this->banManager = new BanManager( | 47 | $this->banManager = new BanManager( |
47 | $this->configManager->get('security.trusted_proxies', []), | 48 | $this->configManager->get('security.trusted_proxies', []), |
48 | $this->configManager->get('security.ban_after'), | 49 | $this->configManager->get('security.ban_after'), |
@@ -86,10 +87,9 @@ class LoginManager | |||
86 | /** | 87 | /** |
87 | * Check user session state and validity (expiration) | 88 | * Check user session state and validity (expiration) |
88 | * | 89 | * |
89 | * @param array $cookie The $_COOKIE array | ||
90 | * @param string $clientIpId Client IP address identifier | 90 | * @param string $clientIpId Client IP address identifier |
91 | */ | 91 | */ |
92 | public function checkLoginState($cookie, $clientIpId) | 92 | public function checkLoginState($clientIpId) |
93 | { | 93 | { |
94 | if (! $this->configManager->exists('credentials.login')) { | 94 | if (! $this->configManager->exists('credentials.login')) { |
95 | // Shaarli is not configured yet | 95 | // Shaarli is not configured yet |
@@ -97,9 +97,7 @@ class LoginManager | |||
97 | return; | 97 | return; |
98 | } | 98 | } |
99 | 99 | ||
100 | if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE]) | 100 | if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) { |
101 | && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken | ||
102 | ) { | ||
103 | // The user client has a valid stay-signed-in cookie | 101 | // The user client has a valid stay-signed-in cookie |
104 | // Session information is updated with the current client information | 102 | // Session information is updated with the current client information |
105 | $this->sessionManager->storeLoginInfo($clientIpId); | 103 | $this->sessionManager->storeLoginInfo($clientIpId); |
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php index 0ac17d9a..82771c24 100644 --- a/application/security/SessionManager.php +++ b/application/security/SessionManager.php | |||
@@ -31,16 +31,21 @@ class SessionManager | |||
31 | /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ | 31 | /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ |
32 | protected $staySignedIn = false; | 32 | protected $staySignedIn = false; |
33 | 33 | ||
34 | /** @var string */ | ||
35 | protected $savePath; | ||
36 | |||
34 | /** | 37 | /** |
35 | * Constructor | 38 | * Constructor |
36 | * | 39 | * |
37 | * @param array $session The $_SESSION array (reference) | 40 | * @param array $session The $_SESSION array (reference) |
38 | * @param ConfigManager $conf ConfigManager instance | 41 | * @param ConfigManager $conf ConfigManager instance |
42 | * @param string $savePath Session save path returned by builtin function session_save_path() | ||
39 | */ | 43 | */ |
40 | public function __construct(& $session, $conf) | 44 | public function __construct(&$session, $conf, string $savePath) |
41 | { | 45 | { |
42 | $this->session = &$session; | 46 | $this->session = &$session; |
43 | $this->conf = $conf; | 47 | $this->conf = $conf; |
48 | $this->savePath = $savePath; | ||
44 | } | 49 | } |
45 | 50 | ||
46 | /** | 51 | /** |
@@ -249,4 +254,9 @@ class SessionManager | |||
249 | 254 | ||
250 | return $this; | 255 | return $this; |
251 | } | 256 | } |
257 | |||
258 | public function getSavePath(): string | ||
259 | { | ||
260 | return $this->savePath; | ||
261 | } | ||
252 | } | 262 | } |
diff --git a/application/updater/Updater.php b/application/updater/Updater.php index f73a7452..4c578528 100644 --- a/application/updater/Updater.php +++ b/application/updater/Updater.php | |||
@@ -39,6 +39,11 @@ class Updater | |||
39 | protected $methods; | 39 | protected $methods; |
40 | 40 | ||
41 | /** | 41 | /** |
42 | * @var string $basePath Shaarli root directory (from HTTP Request) | ||
43 | */ | ||
44 | protected $basePath = null; | ||
45 | |||
46 | /** | ||
42 | * Object constructor. | 47 | * Object constructor. |
43 | * | 48 | * |
44 | * @param array $doneUpdates Updates which are already done. | 49 | * @param array $doneUpdates Updates which are already done. |
@@ -62,11 +67,13 @@ class Updater | |||
62 | * Run all new updates. | 67 | * Run all new updates. |
63 | * Update methods have to start with 'updateMethod' and return true (on success). | 68 | * Update methods have to start with 'updateMethod' and return true (on success). |
64 | * | 69 | * |
70 | * @param string $basePath Shaarli root directory (from HTTP Request) | ||
71 | * | ||
65 | * @return array An array containing ran updates. | 72 | * @return array An array containing ran updates. |
66 | * | 73 | * |
67 | * @throws UpdaterException If something went wrong. | 74 | * @throws UpdaterException If something went wrong. |
68 | */ | 75 | */ |
69 | public function update() | 76 | public function update(string $basePath = null) |
70 | { | 77 | { |
71 | $updatesRan = []; | 78 | $updatesRan = []; |
72 | 79 | ||
@@ -123,16 +130,14 @@ class Updater | |||
123 | } | 130 | } |
124 | 131 | ||
125 | /** | 132 | /** |
126 | * With the Slim routing system, default header link should be `./` instead of `?`. | 133 | * With the Slim routing system, default header link should be `/subfolder/` instead of `?`. |
127 | * Otherwise you can not go back to the home page. Example: `/picture-wall` -> `/picture-wall?` instead of `/`. | 134 | * Otherwise you can not go back to the home page. |
135 | * Example: `/subfolder/picture-wall` -> `/subfolder/picture-wall?` instead of `/subfolder/`. | ||
128 | */ | 136 | */ |
129 | public function updateMethodRelativeHomeLink(): bool | 137 | public function updateMethodRelativeHomeLink(): bool |
130 | { | 138 | { |
131 | $link = trim($this->conf->get('general.header_link')); | 139 | if ('?' === trim($this->conf->get('general.header_link'))) { |
132 | if ($link[0] === '?') { | 140 | $this->conf->set('general.header_link', $this->basePath . '/', true, true); |
133 | $link = './'. ltrim($link, '?'); | ||
134 | |||
135 | $this->conf->set('general.header_link', $link, true, true); | ||
136 | } | 141 | } |
137 | 142 | ||
138 | return true; | 143 | return true; |
@@ -152,7 +157,7 @@ class Updater | |||
152 | && 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match) | 157 | && 1 === preg_match('/^\?([a-zA-Z0-9-_@]{6})($|&|#)/', $bookmark->getUrl(), $match) |
153 | ) { | 158 | ) { |
154 | $updated = true; | 159 | $updated = true; |
155 | $bookmark = $bookmark->setUrl('/shaare/' . $match[1]); | 160 | $bookmark = $bookmark->setUrl($this->basePath . '/shaare/' . $match[1]); |
156 | 161 | ||
157 | $this->bookmarkService->set($bookmark, false); | 162 | $this->bookmarkService->set($bookmark, false); |
158 | } | 163 | } |
@@ -164,4 +169,11 @@ class Updater | |||
164 | 169 | ||
165 | return true; | 170 | return true; |
166 | } | 171 | } |
172 | |||
173 | public function setBasePath(string $basePath): self | ||
174 | { | ||
175 | $this->basePath = $basePath; | ||
176 | |||
177 | return $this; | ||
178 | } | ||
167 | } | 179 | } |