aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorVirtualTam <virtualtam@flibidi.net>2015-11-11 22:49:58 +0100
committerVirtualTam <virtualtam@flibidi.net>2015-11-24 01:12:35 +0100
commit2e28269baed195d58bbe169841eed176b171db76 (patch)
treef743e785edf708454ab53efa13f38e35f10447e6
parentc580024cfbe5f0d290b09157b9665d1b4131d7f4 (diff)
downloadShaarli-2e28269baed195d58bbe169841eed176b171db76.tar.gz
Shaarli-2e28269baed195d58bbe169841eed176b171db76.tar.zst
Shaarli-2e28269baed195d58bbe169841eed176b171db76.zip
install: check file/directory permissions for Shaarli resources
Relates to #40 Relates to #372 Additions: - FileUtils: IOException - ApplicationUtils: - check if Shaarli resources are accessible with sufficient permissions - basic test coverage - index.php: - check access permissions and redirect to an error page if needed: - before running the first installation Modifications: - LinkDB: - factorize datastore write code - check if the datastore (exists AND is writeable) OR (doesn't exist AND its parent dir is writable) - raise an IOException if needed Signed-off-by: VirtualTam <virtualtam@flibidi.net>
-rw-r--r--application/ApplicationUtils.php69
-rw-r--r--application/FileUtils.php19
-rw-r--r--application/LinkDB.php35
-rw-r--r--index.php36
-rw-r--r--tests/ApplicationUtilsTest.php69
-rw-r--r--tests/LinkDBTest.php5
6 files changed, 213 insertions, 20 deletions
diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
new file mode 100644
index 00000000..6fb07f36
--- /dev/null
+++ b/application/ApplicationUtils.php
@@ -0,0 +1,69 @@
1<?php
2/**
3 * Shaarli (application) utilities
4 */
5class ApplicationUtils
6{
7
8 /**
9 * Checks Shaarli has the proper access permissions to its resources
10 *
11 * @param array $globalConfig The $GLOBALS['config'] array
12 *
13 * @return array A list of the detected configuration issues
14 */
15 public static function checkResourcePermissions($globalConfig)
16 {
17 $errors = array();
18
19 // Check script and template directories are readable
20 foreach (array(
21 'application',
22 'inc',
23 'plugins',
24 $globalConfig['RAINTPL_TPL']
25 ) as $path) {
26 if (! is_readable(realpath($path))) {
27 $errors[] = '"'.$path.'" directory is not readable';
28 }
29 }
30
31 // Check cache and data directories are readable and writeable
32 foreach (array(
33 $globalConfig['CACHEDIR'],
34 $globalConfig['DATADIR'],
35 $globalConfig['PAGECACHE'],
36 $globalConfig['RAINTPL_TMP']
37 ) as $path) {
38 if (! is_readable(realpath($path))) {
39 $errors[] = '"'.$path.'" directory is not readable';
40 }
41 if (! is_writable(realpath($path))) {
42 $errors[] = '"'.$path.'" directory is not writable';
43 }
44 }
45
46 // Check configuration files are readable and writeable
47 foreach (array(
48 $globalConfig['CONFIG_FILE'],
49 $globalConfig['DATASTORE'],
50 $globalConfig['IPBANS_FILENAME'],
51 $globalConfig['LOG_FILE'],
52 $globalConfig['UPDATECHECK_FILENAME']
53 ) as $path) {
54 if (! is_file(realpath($path))) {
55 # the file may not exist yet
56 continue;
57 }
58
59 if (! is_readable(realpath($path))) {
60 $errors[] = '"'.$path.'" file is not readable';
61 }
62 if (! is_writable(realpath($path))) {
63 $errors[] = '"'.$path.'" file is not writable';
64 }
65 }
66
67 return $errors;
68 }
69}
diff --git a/application/FileUtils.php b/application/FileUtils.php
new file mode 100644
index 00000000..6a12ef0e
--- /dev/null
+++ b/application/FileUtils.php
@@ -0,0 +1,19 @@
1<?php
2/**
3 * Exception class thrown when a filesystem access failure happens
4 */
5class IOException extends Exception
6{
7 private $path;
8
9 /**
10 * Construct a new IOException
11 *
12 * @param string $path path to the ressource that cannot be accessed
13 */
14 public function __construct($path)
15 {
16 $this->path = $path;
17 $this->message = 'Error accessing '.$this->path;
18 }
19}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 84733505..15fadbc3 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -212,11 +212,7 @@ You use the community supported version of the original Shaarli project, by Seba
212 $this->_links[$link['linkdate']] = $link; 212 $this->_links[$link['linkdate']] = $link;
213 213
214 // Write database to disk 214 // Write database to disk
215 // TODO: raise an exception if the file is not write-able 215 $this->writeDB();
216 file_put_contents(
217 $this->_datastore,
218 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
219 );
220 } 216 }
221 217
222 /** 218 /**
@@ -270,6 +266,28 @@ You use the community supported version of the original Shaarli project, by Seba
270 /** 266 /**
271 * Saves the database from memory to disk 267 * Saves the database from memory to disk
272 * 268 *
269 * @throws IOException the datastore is not writable
270 */
271 private function writeDB()
272 {
273 if (is_file($this->_datastore) && !is_writeable($this->_datastore)) {
274 // The datastore exists but is not writeable
275 throw new IOException($this->_datastore);
276 } else if (!is_file($this->_datastore) && !is_writeable(dirname($this->_datastore))) {
277 // The datastore does not exist and its parent directory is not writeable
278 throw new IOException(dirname($this->_datastore));
279 }
280
281 file_put_contents(
282 $this->_datastore,
283 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
284 );
285
286 }
287
288 /**
289 * Saves the database from memory to disk
290 *
273 * @param string $pageCacheDir page cache directory 291 * @param string $pageCacheDir page cache directory
274 */ 292 */
275 public function savedb($pageCacheDir) 293 public function savedb($pageCacheDir)
@@ -278,10 +296,9 @@ You use the community supported version of the original Shaarli project, by Seba
278 // TODO: raise an Exception instead 296 // TODO: raise an Exception instead
279 die('You are not authorized to change the database.'); 297 die('You are not authorized to change the database.');
280 } 298 }
281 file_put_contents( 299
282 $this->_datastore, 300 $this->writeDB();
283 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix 301
284 );
285 invalidateCaches($pageCacheDir); 302 invalidateCaches($pageCacheDir);
286 } 303 }
287 304
diff --git a/index.php b/index.php
index be181a2c..654f7f8f 100644
--- a/index.php
+++ b/index.php
@@ -44,6 +44,9 @@ $GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'
44// Banned IPs 44// Banned IPs
45$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php'; 45$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php';
46 46
47// Access log
48$GLOBALS['config']['LOG_FILE'] = $GLOBALS['config']['DATADIR'].'/log.txt';
49
47// For updates check of Shaarli 50// For updates check of Shaarli
48$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; 51$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt';
49 52
@@ -52,7 +55,7 @@ $GLOBALS['config']['RAINTPL_TMP'] = 'tmp/';
52// Raintpl template directory (keep the trailing slash!) 55// Raintpl template directory (keep the trailing slash!)
53$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/'; 56$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/';
54 57
55// Thuumbnail cache directory 58// Thumbnail cache directory
56$GLOBALS['config']['CACHEDIR'] = 'cache'; 59$GLOBALS['config']['CACHEDIR'] = 'cache';
57 60
58// Atom & RSS feed cache directory 61// Atom & RSS feed cache directory
@@ -141,8 +144,10 @@ if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
141} 144}
142 145
143// Shaarli library 146// Shaarli library
147require_once 'application/ApplicationUtils.php';
144require_once 'application/Cache.php'; 148require_once 'application/Cache.php';
145require_once 'application/CachedPage.php'; 149require_once 'application/CachedPage.php';
150require_once 'application/FileUtils.php';
146require_once 'application/HttpUtils.php'; 151require_once 'application/HttpUtils.php';
147require_once 'application/LinkDB.php'; 152require_once 'application/LinkDB.php';
148require_once 'application/TimeZone.php'; 153require_once 'application/TimeZone.php';
@@ -155,9 +160,9 @@ require_once 'application/Router.php';
155// Ensure the PHP version is supported 160// Ensure the PHP version is supported
156try { 161try {
157 checkPHPVersion('5.3', PHP_VERSION); 162 checkPHPVersion('5.3', PHP_VERSION);
158} catch(Exception $e) { 163} catch(Exception $exc) {
159 header('Content-Type: text/plain; charset=utf-8'); 164 header('Content-Type: text/plain; charset=utf-8');
160 echo $e->getMessage(); 165 echo $exc->getMessage();
161 exit; 166 exit;
162} 167}
163 168
@@ -216,9 +221,6 @@ header("Cache-Control: no-store, no-cache, must-revalidate");
216header("Cache-Control: post-check=0, pre-check=0", false); 221header("Cache-Control: post-check=0, pre-check=0", false);
217header("Pragma: no-cache"); 222header("Pragma: no-cache");
218 223
219// Directories creations (Note that your web host may require different rights than 705.)
220if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory.</pre>');
221
222// Handling of old config file which do not have the new parameters. 224// Handling of old config file which do not have the new parameters.
223if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER)); 225if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
224if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get(); 226if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
@@ -228,8 +230,24 @@ if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=fa
228if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?'; 230if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
229// I really need to rewrite Shaarli with a proper configuation manager. 231// I really need to rewrite Shaarli with a proper configuation manager.
230 232
231// Run config screen if first run:
232if (! is_file($GLOBALS['config']['CONFIG_FILE'])) { 233if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
234 // Ensure Shaarli has proper access to its resources
235 $errors = ApplicationUtils::checkResourcePermissions($GLOBALS['config']);
236
237 if ($errors != array()) {
238 $message = '<p>Insufficient permissions:</p><ul>';
239
240 foreach ($errors as $error) {
241 $message .= '<li>'.$error.'</li>';
242 }
243 $message .= '</ul>';
244
245 header('Content-Type: text/html; charset=utf-8');
246 echo $message;
247 exit;
248 }
249
250 // Display the installation form if no existing config is found
233 install(); 251 install();
234} 252}
235 253
@@ -319,7 +337,7 @@ function checkUpdate()
319function logm($message) 337function logm($message)
320{ 338{
321 $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n"; 339 $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n";
322 file_put_contents($GLOBALS['config']['DATADIR'].'/log.txt',$t,FILE_APPEND); 340 file_put_contents($GLOBAL['config']['LOG_FILE'], $t, FILE_APPEND);
323} 341}
324 342
325// In a string, converts URLs to clickable links. 343// In a string, converts URLs to clickable links.
@@ -1461,7 +1479,7 @@ function renderPage()
1461 $value['tags']=trim(implode(' ',$tags)); 1479 $value['tags']=trim(implode(' ',$tags));
1462 $LINKSDB[$key]=$value; 1480 $LINKSDB[$key]=$value;
1463 } 1481 }
1464 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. 1482 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']);
1465 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>'; 1483 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
1466 exit; 1484 exit;
1467 } 1485 }
diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php
new file mode 100644
index 00000000..9a99c6c6
--- /dev/null
+++ b/tests/ApplicationUtilsTest.php
@@ -0,0 +1,69 @@
1<?php
2/**
3 * ApplicationUtils' tests
4 */
5
6require_once 'application/ApplicationUtils.php';
7
8
9/**
10 * Unitary tests for Shaarli utilities
11 */
12class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
13{
14 /**
15 * Checks resource permissions for the current Shaarli installation
16 */
17 public function testCheckCurrentResourcePermissions()
18 {
19 $config = array(
20 'CACHEDIR' => 'cache',
21 'CONFIG_FILE' => 'data/config.php',
22 'DATADIR' => 'data',
23 'DATASTORE' => 'data/datastore.php',
24 'IPBANS_FILENAME' => 'data/ipbans.php',
25 'LOG_FILE' => 'data/log.txt',
26 'PAGECACHE' => 'pagecache',
27 'RAINTPL_TMP' => 'tmp',
28 'RAINTPL_TPL' => 'tpl',
29 'UPDATECHECK_FILENAME' => 'data/lastupdatecheck.txt'
30 );
31 $this->assertEquals(
32 array(),
33 ApplicationUtils::checkResourcePermissions($config)
34 );
35 }
36
37 /**
38 * Checks resource permissions for a non-existent Shaarli installation
39 */
40 public function testCheckCurrentResourcePermissionsErrors()
41 {
42 $config = array(
43 'CACHEDIR' => 'null/cache',
44 'CONFIG_FILE' => 'null/data/config.php',
45 'DATADIR' => 'null/data',
46 'DATASTORE' => 'null/data/store.php',
47 'IPBANS_FILENAME' => 'null/data/ipbans.php',
48 'LOG_FILE' => 'null/data/log.txt',
49 'PAGECACHE' => 'null/pagecache',
50 'RAINTPL_TMP' => 'null/tmp',
51 'RAINTPL_TPL' => 'null/tpl',
52 'UPDATECHECK_FILENAME' => 'null/data/lastupdatecheck.txt'
53 );
54 $this->assertEquals(
55 array(
56 '"null/tpl" directory is not readable',
57 '"null/cache" directory is not readable',
58 '"null/cache" directory is not writable',
59 '"null/data" directory is not readable',
60 '"null/data" directory is not writable',
61 '"null/pagecache" directory is not readable',
62 '"null/pagecache" directory is not writable',
63 '"null/tmp" directory is not readable',
64 '"null/tmp" directory is not writable'
65 ),
66 ApplicationUtils::checkResourcePermissions($config)
67 );
68 }
69}
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 451f1d6f..8929713d 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -4,6 +4,7 @@
4 */ 4 */
5 5
6require_once 'application/Cache.php'; 6require_once 'application/Cache.php';
7require_once 'application/FileUtils.php';
7require_once 'application/LinkDB.php'; 8require_once 'application/LinkDB.php';
8require_once 'application/Utils.php'; 9require_once 'application/Utils.php';
9require_once 'tests/utils/ReferenceLinkDB.php'; 10require_once 'tests/utils/ReferenceLinkDB.php';
@@ -87,8 +88,8 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
87 /** 88 /**
88 * Attempt to instantiate a LinkDB whereas the datastore is not writable 89 * Attempt to instantiate a LinkDB whereas the datastore is not writable
89 * 90 *
90 * @expectedException PHPUnit_Framework_Error_Warning 91 * @expectedException IOException
91 * @expectedExceptionMessageRegExp /failed to open stream: No such file or directory/ 92 * @expectedExceptionMessageRegExp /Error accessing null/
92 */ 93 */
93 public function testConstructDatastoreNotWriteable() 94 public function testConstructDatastoreNotWriteable()
94 { 95 {