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>
This commit is contained in:
parent
c580024cfb
commit
2e28269bae
6 changed files with 213 additions and 20 deletions
69
application/ApplicationUtils.php
Normal file
69
application/ApplicationUtils.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
/**
|
||||
* Shaarli (application) utilities
|
||||
*/
|
||||
class ApplicationUtils
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks Shaarli has the proper access permissions to its resources
|
||||
*
|
||||
* @param array $globalConfig The $GLOBALS['config'] array
|
||||
*
|
||||
* @return array A list of the detected configuration issues
|
||||
*/
|
||||
public static function checkResourcePermissions($globalConfig)
|
||||
{
|
||||
$errors = array();
|
||||
|
||||
// Check script and template directories are readable
|
||||
foreach (array(
|
||||
'application',
|
||||
'inc',
|
||||
'plugins',
|
||||
$globalConfig['RAINTPL_TPL']
|
||||
) as $path) {
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" directory is not readable';
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache and data directories are readable and writeable
|
||||
foreach (array(
|
||||
$globalConfig['CACHEDIR'],
|
||||
$globalConfig['DATADIR'],
|
||||
$globalConfig['PAGECACHE'],
|
||||
$globalConfig['RAINTPL_TMP']
|
||||
) as $path) {
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" directory is not readable';
|
||||
}
|
||||
if (! is_writable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" directory is not writable';
|
||||
}
|
||||
}
|
||||
|
||||
// Check configuration files are readable and writeable
|
||||
foreach (array(
|
||||
$globalConfig['CONFIG_FILE'],
|
||||
$globalConfig['DATASTORE'],
|
||||
$globalConfig['IPBANS_FILENAME'],
|
||||
$globalConfig['LOG_FILE'],
|
||||
$globalConfig['UPDATECHECK_FILENAME']
|
||||
) as $path) {
|
||||
if (! is_file(realpath($path))) {
|
||||
# the file may not exist yet
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_readable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" file is not readable';
|
||||
}
|
||||
if (! is_writable(realpath($path))) {
|
||||
$errors[] = '"'.$path.'" file is not writable';
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
19
application/FileUtils.php
Normal file
19
application/FileUtils.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
/**
|
||||
* Exception class thrown when a filesystem access failure happens
|
||||
*/
|
||||
class IOException extends Exception
|
||||
{
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Construct a new IOException
|
||||
*
|
||||
* @param string $path path to the ressource that cannot be accessed
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->message = 'Error accessing '.$this->path;
|
||||
}
|
||||
}
|
|
@ -212,11 +212,7 @@ private function _checkDB()
|
|||
$this->_links[$link['linkdate']] = $link;
|
||||
|
||||
// Write database to disk
|
||||
// TODO: raise an exception if the file is not write-able
|
||||
file_put_contents(
|
||||
$this->_datastore,
|
||||
self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
|
||||
);
|
||||
$this->writeDB();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -267,6 +263,28 @@ private function _readDB()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the database from memory to disk
|
||||
*
|
||||
* @throws IOException the datastore is not writable
|
||||
*/
|
||||
private function writeDB()
|
||||
{
|
||||
if (is_file($this->_datastore) && !is_writeable($this->_datastore)) {
|
||||
// The datastore exists but is not writeable
|
||||
throw new IOException($this->_datastore);
|
||||
} else if (!is_file($this->_datastore) && !is_writeable(dirname($this->_datastore))) {
|
||||
// The datastore does not exist and its parent directory is not writeable
|
||||
throw new IOException(dirname($this->_datastore));
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$this->_datastore,
|
||||
self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the database from memory to disk
|
||||
*
|
||||
|
@ -278,10 +296,9 @@ public function savedb($pageCacheDir)
|
|||
// TODO: raise an Exception instead
|
||||
die('You are not authorized to change the database.');
|
||||
}
|
||||
file_put_contents(
|
||||
$this->_datastore,
|
||||
self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
|
||||
);
|
||||
|
||||
$this->writeDB();
|
||||
|
||||
invalidateCaches($pageCacheDir);
|
||||
}
|
||||
|
||||
|
|
36
index.php
36
index.php
|
@ -44,6 +44,9 @@
|
|||
// Banned IPs
|
||||
$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php';
|
||||
|
||||
// Access log
|
||||
$GLOBALS['config']['LOG_FILE'] = $GLOBALS['config']['DATADIR'].'/log.txt';
|
||||
|
||||
// For updates check of Shaarli
|
||||
$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt';
|
||||
|
||||
|
@ -52,7 +55,7 @@
|
|||
// Raintpl template directory (keep the trailing slash!)
|
||||
$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/';
|
||||
|
||||
// Thuumbnail cache directory
|
||||
// Thumbnail cache directory
|
||||
$GLOBALS['config']['CACHEDIR'] = 'cache';
|
||||
|
||||
// Atom & RSS feed cache directory
|
||||
|
@ -141,8 +144,10 @@
|
|||
}
|
||||
|
||||
// Shaarli library
|
||||
require_once 'application/ApplicationUtils.php';
|
||||
require_once 'application/Cache.php';
|
||||
require_once 'application/CachedPage.php';
|
||||
require_once 'application/FileUtils.php';
|
||||
require_once 'application/HttpUtils.php';
|
||||
require_once 'application/LinkDB.php';
|
||||
require_once 'application/TimeZone.php';
|
||||
|
@ -155,9 +160,9 @@
|
|||
// Ensure the PHP version is supported
|
||||
try {
|
||||
checkPHPVersion('5.3', PHP_VERSION);
|
||||
} catch(Exception $e) {
|
||||
} catch(Exception $exc) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $e->getMessage();
|
||||
echo $exc->getMessage();
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -216,9 +221,6 @@ function stripslashes_deep($value) { $value = is_array($value) ? array_map('stri
|
|||
header("Cache-Control: post-check=0, pre-check=0", false);
|
||||
header("Pragma: no-cache");
|
||||
|
||||
// Directories creations (Note that your web host may require different rights than 705.)
|
||||
if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory.</pre>');
|
||||
|
||||
// Handling of old config file which do not have the new parameters.
|
||||
if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
|
||||
if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
|
||||
|
@ -228,8 +230,24 @@ function stripslashes_deep($value) { $value = is_array($value) ? array_map('stri
|
|||
if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
|
||||
// I really need to rewrite Shaarli with a proper configuation manager.
|
||||
|
||||
// Run config screen if first run:
|
||||
if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
|
||||
// Ensure Shaarli has proper access to its resources
|
||||
$errors = ApplicationUtils::checkResourcePermissions($GLOBALS['config']);
|
||||
|
||||
if ($errors != array()) {
|
||||
$message = '<p>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();
|
||||
}
|
||||
|
||||
|
@ -319,7 +337,7 @@ function checkUpdate()
|
|||
function logm($message)
|
||||
{
|
||||
$t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n";
|
||||
file_put_contents($GLOBALS['config']['DATADIR'].'/log.txt',$t,FILE_APPEND);
|
||||
file_put_contents($GLOBAL['config']['LOG_FILE'], $t, FILE_APPEND);
|
||||
}
|
||||
|
||||
// In a string, converts URLs to clickable links.
|
||||
|
@ -1461,7 +1479,7 @@ function renderPage()
|
|||
$value['tags']=trim(implode(' ',$tags));
|
||||
$LINKSDB[$key]=$value;
|
||||
}
|
||||
$LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
|
||||
$LINKSDB->savedb($GLOBALS['config']['PAGECACHE']);
|
||||
echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
|
||||
exit;
|
||||
}
|
||||
|
|
69
tests/ApplicationUtilsTest.php
Normal file
69
tests/ApplicationUtilsTest.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
/**
|
||||
* ApplicationUtils' tests
|
||||
*/
|
||||
|
||||
require_once 'application/ApplicationUtils.php';
|
||||
|
||||
|
||||
/**
|
||||
* Unitary tests for Shaarli utilities
|
||||
*/
|
||||
class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* Checks resource permissions for the current Shaarli installation
|
||||
*/
|
||||
public function testCheckCurrentResourcePermissions()
|
||||
{
|
||||
$config = array(
|
||||
'CACHEDIR' => 'cache',
|
||||
'CONFIG_FILE' => 'data/config.php',
|
||||
'DATADIR' => 'data',
|
||||
'DATASTORE' => 'data/datastore.php',
|
||||
'IPBANS_FILENAME' => 'data/ipbans.php',
|
||||
'LOG_FILE' => 'data/log.txt',
|
||||
'PAGECACHE' => 'pagecache',
|
||||
'RAINTPL_TMP' => 'tmp',
|
||||
'RAINTPL_TPL' => 'tpl',
|
||||
'UPDATECHECK_FILENAME' => 'data/lastupdatecheck.txt'
|
||||
);
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
ApplicationUtils::checkResourcePermissions($config)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks resource permissions for a non-existent Shaarli installation
|
||||
*/
|
||||
public function testCheckCurrentResourcePermissionsErrors()
|
||||
{
|
||||
$config = array(
|
||||
'CACHEDIR' => 'null/cache',
|
||||
'CONFIG_FILE' => 'null/data/config.php',
|
||||
'DATADIR' => 'null/data',
|
||||
'DATASTORE' => 'null/data/store.php',
|
||||
'IPBANS_FILENAME' => 'null/data/ipbans.php',
|
||||
'LOG_FILE' => 'null/data/log.txt',
|
||||
'PAGECACHE' => 'null/pagecache',
|
||||
'RAINTPL_TMP' => 'null/tmp',
|
||||
'RAINTPL_TPL' => 'null/tpl',
|
||||
'UPDATECHECK_FILENAME' => 'null/data/lastupdatecheck.txt'
|
||||
);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'"null/tpl" directory is not readable',
|
||||
'"null/cache" directory is not readable',
|
||||
'"null/cache" directory is not writable',
|
||||
'"null/data" directory is not readable',
|
||||
'"null/data" directory is not writable',
|
||||
'"null/pagecache" directory is not readable',
|
||||
'"null/pagecache" directory is not writable',
|
||||
'"null/tmp" directory is not readable',
|
||||
'"null/tmp" directory is not writable'
|
||||
),
|
||||
ApplicationUtils::checkResourcePermissions($config)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
require_once 'application/Cache.php';
|
||||
require_once 'application/FileUtils.php';
|
||||
require_once 'application/LinkDB.php';
|
||||
require_once 'application/Utils.php';
|
||||
require_once 'tests/utils/ReferenceLinkDB.php';
|
||||
|
@ -87,8 +88,8 @@ public function testConstructLoggedOut()
|
|||
/**
|
||||
* Attempt to instantiate a LinkDB whereas the datastore is not writable
|
||||
*
|
||||
* @expectedException PHPUnit_Framework_Error_Warning
|
||||
* @expectedExceptionMessageRegExp /failed to open stream: No such file or directory/
|
||||
* @expectedException IOException
|
||||
* @expectedExceptionMessageRegExp /Error accessing null/
|
||||
*/
|
||||
public function testConstructDatastoreNotWriteable()
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue