Merge pull request #375 from virtualtam/utils/permissions
tools: check file/directory permissions for Shaarli resources
This commit is contained in:
commit
0def004963
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;
|
$this->_links[$link['linkdate']] = $link;
|
||||||
|
|
||||||
// Write database to disk
|
// Write database to disk
|
||||||
// TODO: raise an exception if the file is not write-able
|
$this->writeDB();
|
||||||
file_put_contents(
|
|
||||||
$this->_datastore,
|
|
||||||
self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
* Saves the database from memory to disk
|
||||||
*
|
*
|
||||||
|
@ -278,10 +296,9 @@ public function savedb($pageCacheDir)
|
||||||
// TODO: raise an Exception instead
|
// TODO: raise an Exception instead
|
||||||
die('You are not authorized to change the database.');
|
die('You are not authorized to change the database.');
|
||||||
}
|
}
|
||||||
file_put_contents(
|
|
||||||
$this->_datastore,
|
$this->writeDB();
|
||||||
self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
|
|
||||||
);
|
|
||||||
invalidateCaches($pageCacheDir);
|
invalidateCaches($pageCacheDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
36
index.php
36
index.php
|
@ -44,6 +44,9 @@
|
||||||
// Banned IPs
|
// Banned IPs
|
||||||
$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php';
|
$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
|
// For updates check of Shaarli
|
||||||
$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt';
|
$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt';
|
||||||
|
|
||||||
|
@ -52,7 +55,7 @@
|
||||||
// Raintpl template directory (keep the trailing slash!)
|
// Raintpl template directory (keep the trailing slash!)
|
||||||
$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/';
|
$GLOBALS['config']['RAINTPL_TPL'] = 'tpl/';
|
||||||
|
|
||||||
// Thuumbnail cache directory
|
// Thumbnail cache directory
|
||||||
$GLOBALS['config']['CACHEDIR'] = 'cache';
|
$GLOBALS['config']['CACHEDIR'] = 'cache';
|
||||||
|
|
||||||
// Atom & RSS feed cache directory
|
// Atom & RSS feed cache directory
|
||||||
|
@ -141,8 +144,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shaarli library
|
// Shaarli library
|
||||||
|
require_once 'application/ApplicationUtils.php';
|
||||||
require_once 'application/Cache.php';
|
require_once 'application/Cache.php';
|
||||||
require_once 'application/CachedPage.php';
|
require_once 'application/CachedPage.php';
|
||||||
|
require_once 'application/FileUtils.php';
|
||||||
require_once 'application/HttpUtils.php';
|
require_once 'application/HttpUtils.php';
|
||||||
require_once 'application/LinkDB.php';
|
require_once 'application/LinkDB.php';
|
||||||
require_once 'application/TimeZone.php';
|
require_once 'application/TimeZone.php';
|
||||||
|
@ -155,9 +160,9 @@
|
||||||
// Ensure the PHP version is supported
|
// Ensure the PHP version is supported
|
||||||
try {
|
try {
|
||||||
checkPHPVersion('5.3', PHP_VERSION);
|
checkPHPVersion('5.3', PHP_VERSION);
|
||||||
} catch(Exception $e) {
|
} catch(Exception $exc) {
|
||||||
header('Content-Type: text/plain; charset=utf-8');
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
echo $e->getMessage();
|
echo $exc->getMessage();
|
||||||
exit;
|
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("Cache-Control: post-check=0, pre-check=0", false);
|
||||||
header("Pragma: no-cache");
|
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.
|
// 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['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
|
||||||
if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
|
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']='?';
|
if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
|
||||||
// I really need to rewrite Shaarli with a proper configuation manager.
|
// I really need to rewrite Shaarli with a proper configuation manager.
|
||||||
|
|
||||||
// Run config screen if first run:
|
|
||||||
if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
|
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();
|
install();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +337,7 @@ function checkUpdate()
|
||||||
function logm($message)
|
function logm($message)
|
||||||
{
|
{
|
||||||
$t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n";
|
$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.
|
// In a string, converts URLs to clickable links.
|
||||||
|
@ -1461,7 +1479,7 @@ function renderPage()
|
||||||
$value['tags']=trim(implode(' ',$tags));
|
$value['tags']=trim(implode(' ',$tags));
|
||||||
$LINKSDB[$key]=$value;
|
$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>';
|
echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
|
||||||
exit;
|
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/Cache.php';
|
||||||
|
require_once 'application/FileUtils.php';
|
||||||
require_once 'application/LinkDB.php';
|
require_once 'application/LinkDB.php';
|
||||||
require_once 'application/Utils.php';
|
require_once 'application/Utils.php';
|
||||||
require_once 'tests/utils/ReferenceLinkDB.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
|
* Attempt to instantiate a LinkDB whereas the datastore is not writable
|
||||||
*
|
*
|
||||||
* @expectedException PHPUnit_Framework_Error_Warning
|
* @expectedException IOException
|
||||||
* @expectedExceptionMessageRegExp /failed to open stream: No such file or directory/
|
* @expectedExceptionMessageRegExp /Error accessing null/
|
||||||
*/
|
*/
|
||||||
public function testConstructDatastoreNotWriteable()
|
public function testConstructDatastoreNotWriteable()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue