- added hooks to general vfs methods to allow apps to monitore file access, creating, modification, removal

- new $user parameter for egw_vfs::check_access($path, $check, $stat=null, $user=null) to check access to a file for a user, who is NOT the current user
This commit is contained in:
Ralf Becker 2011-06-23 18:34:47 +00:00
parent 164871ec02
commit 7282d42f52
6 changed files with 241 additions and 33 deletions

View File

@ -1095,18 +1095,26 @@ class filemanager_ui
'align' => 'right',
));
}
if (($extra_tab = egw_vfs::getExtraInfo($path,$content)))
if (($extra_tabs = egw_vfs::getExtraInfo($path,$content)))
{
$tabs =& $tpl->get_widget_by_name('tabs=general|perms|eacl|preview|custom');
foreach(isset($extra_tabs[0]) ? $extra_tabs : array($extra_tabs) as $extra_tab)
{
$tabs['name'] .= '|'.$extra_tab['name'];
$tabs['label'] .= '|'.$extra_tab['label'];
$tabs['help'] .= '|'.$extra_tab['help'];
if ($extra_tab['data'] && is_array($extra_tab['data']))
{
$content += $extra_tab['data'];
$content = array_merge($content, $extra_tab['data']);
}
if ($extra_tab['preserve'] && is_array($extra_tab['preserve']))
{
$preserve = array_merge($preserve, $extra_tab['preserve']);
}
if ($extra_tab['readonlys'] && is_array($extra_tab['readonlys']))
{
$readonlys += $extra_tab['readonlys'];
$readonlys = array_merge($content, $extra_tab['readonlys']);
}
}
}
$GLOBALS['egw_info']['flags']['java_script'] = "<script>window.focus();</script>\n";

View File

@ -1229,11 +1229,19 @@ class egw_link extends solink
/**
* Delete the diverse caches for $app/$id
*
* @param string $app
* @param int|string $id
* @param string $app app-name or null to delete the whole cache
* @param int|string $id id or null to delete only file_access cache of given app (keeps title cache, if app implements file_access!)
*/
private static function delete_cache($app,$id)
public static function delete_cache($app,$id)
{
if (empty($app) || empty($id))
{
self::$file_access_cache = array();
if (empty($app) || !self::get_registry($app, 'file_access'))
{
self::$title_cache = array();
}
}
unset(self::$title_cache[$app.':'.$id]);
unset(self::$file_access_cache[$app.':'.$id]);
}

View File

@ -760,14 +760,50 @@ class egw_vfs extends vfs_stream_wrapper
* The stream_wrapper interface checks is_{readable|writable|executable} against the webservers uid,
* which is wrong in case of our vfs, as we use the current users id and memberships
*
* @param array|string $path stat array or path
* @param string $path path
* @param int $check mode to check: one or more or'ed together of: 4 = egw_vfs::READABLE,
* 2 = egw_vfs::WRITABLE, 1 = egw_vfs::EXECUTABLE
* @param array $stat=null stat array, to not query it again
* @param int $user=null user used for check, if not current user (egw_vfs::$user)
* @return boolean
*/
static function check_access($path,$check,$stat=null)
static function check_access($path, $check, array $stat=null, $user=null)
{
if (!$stat && $user && $user != self::$user)
{
static $path_user_stat = array();
$backup_user = self::$user;
self::$user = $user;
if (!isset($path_user_stat[$path]) || !isset($path_user_stat[$path][$user]))
{
self::clearstatcache($path);
$path_user_stat[$path][$user] = self::url_stat($path, 0);
}
if (($stat = $path_user_stat[$path][$user]))
{
// some backend mounts use $user:$pass in their url, for them we have to deny access!
if (strpos(self::resolve_url($path, false, false, false), '$user') !== false)
{
$ret = false;
}
else
{
$ret = self::check_access($path, $check, $stat);
}
}
else
{
$ret = false; // no access, if we can not stat the file
}
self::$user = $backup_user;
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check,$user) ".array2string($ret));
return $ret;
}
if (self::$is_root)
{
return true;
@ -819,12 +855,7 @@ class egw_vfs extends vfs_stream_wrapper
// check if there's a group access and we have the right membership
if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid'])
{
static $memberships;
if (is_null($memberships))
{
$memberships = $GLOBALS['egw']->accounts->memberships(self::$user,true);
}
if (in_array(-abs($stat['gid']),$memberships))
if (in_array(-abs($stat['gid']), $GLOBALS['egw']->accounts->memberships(self::$user, true)))
{
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!");
return true;
@ -1420,7 +1451,24 @@ class egw_vfs extends vfs_stream_wrapper
*/
static function getExtraInfo($path,array $content=null)
{
return self::_call_on_backend('extra_info',array($path,$content),true); // true = fail silent if backend does NOT support it
$extra = array();
if (($extra_info = self::_call_on_backend('extra_info',array($path,$content),true))) // true = fail silent if backend does NOT support it
{
$extra[] = $extra_info;
}
if (($vfs_extra = $GLOBALS['egw']->hooks->process(array(
'location' => 'vfs_extra',
'path' => $path,
'content' => $content,
))))
{
foreach($vfs_extra as $app => $data)
{
$extra = $extra ? array_merge($extra, $data) : $data;
}
}
return $extra;
}
/**

View File

@ -59,6 +59,26 @@ class links_stream_wrapper extends links_stream_wrapper_parent
*/
const DEBUG = false;
/**
* Clears our stat-cache
*
* Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes!
*
* We have to clear file_access cache of egw_link, as our ACL depends on it!
*
* @param string $path='/apps' to be able to clear only file_access cache of a certain app
*/
public static function clearstatcache($path='/apps')
{
//error_log(__METHOD__."('$path')");
if ($path[0] != '/') $path = parse_url($path, PHP_URL_PATH);
list(,,$app) = explode('/',$path);
egw_link::delete_cache($app, null); // null = delete only file_access cache, if app implements it, otherwise title cache is deleted too
// need to call sqlfs clearstatcache too
parent::clearstatcache($path);
}
/**
* Implements ACL based on the access of the user to the entry the files are linked to.
*

View File

@ -160,6 +160,21 @@ class sqlfs_stream_wrapper implements iface_stream_wrapper
*/
static public $extra_columns = ',fs_link';
/**
* Clears our stat-cache
*
* Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes!
*
* @param string $path='/'
*/
public static function clearstatcache($path='/')
{
//error_log(__METHOD__."('$path')");
self::$stat_cache = array();
$GLOBALS['egw']->session->appsession('extended_acl',self::EACL_APPNAME,self::$extended_acl = array());
}
/**
* This method is called immediately after your stream object is created.
*

View File

@ -72,6 +72,30 @@ class vfs_stream_wrapper implements iface_stream_wrapper
* @var ressource
*/
private $opened_stream;
/**
* Mode of opened stream, eg. "r" or "w"
*
* @var string
*/
private $opened_stream_mode;
/**
* Path of opened stream
*
* @var string
*/
private $opened_stream_path;
/**
* URL of opened stream
*
* @var string
*/
private $opened_stream_url;
/**
* Opened stream is a new file, false for existing files
*
* @var boolean
*/
private $opened_stream_is_new;
/**
* directory-ressouce this class is opened for by dir_open
*
@ -110,16 +134,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper
*
* @param string $path
* @param boolean $file_exists=true true if file needs to exists, false if not
* @param boolean $resolve_last_symlink=true
* @param array|boolean &$stat=null on return: stat of existing file or false for non-existing files
* @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry
*/
static function resolve_url_symlinks($path,$file_exists=true,$resolve_last_symlink=true)
static function resolve_url_symlinks($path,$file_exists=true,$resolve_last_symlink=true,&$stat=null)
{
$path = self::get_path($path);
if (!($stat = self::url_stat($path,$resolve_last_symlink?0:STREAM_URL_STAT_LINK)) && !$file_exists)
{
$ret = self::check_symlink_components($path,0,$url);
if (self::LOG_LEVEL > 1) $log = " (check_symlink_components('$path',0,'$url') = $ret)";
$stat = self::check_symlink_components($path,0,$url);
if (self::LOG_LEVEL > 1) $log = " (check_symlink_components('$path',0,'$url') = $stat)";
}
else
{
@ -139,24 +165,27 @@ class vfs_stream_wrapper implements iface_stream_wrapper
*
* @param string $path
* @param boolean $do_symlink=true is a direct match allowed, default yes (must be false for a lstat or readlink!)
* @param boolean $use_symlinkcache=true
* @param boolean $replace_user_pass_host=true replace $user,$pass,$host in url, default true, if false result is not cached
* @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry
*/
static function resolve_url($path,$do_symlink=true,$use_symlinkcache=true)
static function resolve_url($path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true)
{
static $cache = array();
$path = self::get_path($path);
// we do some caching here
if (isset($cache[$path]))
if (isset($cache[$path]) && $replace_user_pass_host)
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '{$cache[$path]}' (from cache)");
return $cache[$path];
}
// check if we can already resolve path (or a part of it) with a known symlinks
if ($use_symlinkcache)
{
$path = self::symlinkCache_resolve($path,$do_symlink);
}
// setting default user, passwd and domain, if it's not contained int the url
static $defaults;
if (is_null($defaults))
@ -188,13 +217,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper
}
$url = egw_vfs::concat($url,substr($parts['path'],strlen($mounted)));
if ($replace_user_pass_host)
{
$url = str_replace(array('$user','$pass','$host'),array($parts['user'],$parts['pass'],$parts['host']),$url);
}
if ($parts['query']) $url .= '?'.$parts['query'];
if ($parts['fragment']) $url .= '#'.$parts['fragment'];
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$url'");
return $cache[$path] = $url;
if ($replace_user_pass_host) $cache[$path] = $url;
return $url;
}
}
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path') can't resolve path!\n");
@ -237,7 +271,7 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
$this->opened_stream = null;
if (!($url = self::resolve_url_symlinks($path,$mode[0]=='r')))
if (!($url = self::resolve_url_symlinks($path,$mode[0]=='r',true,$stat)))
{
return false;
}
@ -245,6 +279,11 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return false;
}
$this->opened_stream_mode = $mode;
$this->opened_stream_path = $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH);
$this->opened_stream_url = $url;
$this->opened_stream_is_new = !$stat;
return true;
}
@ -252,12 +291,24 @@ class vfs_stream_wrapper implements iface_stream_wrapper
* This method is called when the stream is closed, using fclose().
*
* You must release any resources that were locked or allocated by the stream.
*
* VFS calls either "vfs_read", "vfs_added" or "vfs_modified" hook
*/
function stream_close ( )
{
$ret = fclose($this->opened_stream);
$this->opened_stream = null;
if (isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks))
{
$GLOBALS['egw']->hooks->process(array(
'location' => str_replace('b','',$this->opened_stream_mode) == 'r' ? 'vfs_read' :
($this->opened_stream_is_new ? 'vfs_added' : 'vfs_modified'),
'path' => $this->opened_stream_path,
'mode' => $this->opened_stream_mode,
'url' => $this->opened_stream_url,
),'',true);
}
$this->opened_stream = $this->opened_stream_mode = $this->opened_stream_path = $this->opened_stream_url = $this->opened_stream_is_new = null;
return $ret;
}
@ -384,6 +435,15 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return false;
}
// call "vfs_unlink" hook, before unlink
if (isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks))
{
$GLOBALS['egw']->hooks->process(array(
'location' => 'vfs_unlink',
'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH),
'url' => $url,
),'',true);
}
self::symlinkCache_remove($path);
return unlink($url);
}
@ -428,6 +488,17 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
error_log(__METHOD__."('$path_from','$path_to') url_from='$url_from', url_to='$url_to' returning ".array2string($ret));
}
// call "vfs_rename" hook
if ($ret && isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks))
{
$GLOBALS['egw']->hooks->process(array(
'location' => 'vfs_rename',
'from' => $path_from[0] == '/' ? $path_from : parse_url($path_from, PHP_URL_PATH),
'to' => $path_to[0] == '/' ? $path_to : parse_url($path_to, PHP_URL_PATH),
'url_from' => $url_from,
'url_to' => $url_to,
),'',true);
}
return $ret;
}
@ -448,7 +519,18 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return false;
}
return mkdir($url,$mode,$options);
$ret = mkdir($url,$mode,$options);
// call "vfs_mkdir" hook
if ($ret && isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks))
{
$GLOBALS['egw']->hooks->process(array(
'location' => 'vfs_mkdir',
'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH),
'url' => $url,
),'',true);
}
return $ret;
}
/**
@ -467,6 +549,15 @@ class vfs_stream_wrapper implements iface_stream_wrapper
{
return false;
}
// call "vfs_rmdir" hook, before removing
if (isset($GLOBALS['egw']) && isset($GLOBALS['egw']->hooks))
{
$GLOBALS['egw']->hooks->process(array(
'location' => 'vfs_rmdir',
'path' => $path[0] == '/' ? $path : parse_url($path, PHP_URL_PATH),
'url' => $url,
),'',true);
}
self::symlinkCache_remove($path);
return rmdir($url);
}
@ -932,6 +1023,24 @@ class vfs_stream_wrapper implements iface_stream_wrapper
return isset($target) ? $target : $path;
}
/**
* Clears our internal stat and symlink cache
*
* Normaly not necessary, as it is automatically cleared/updated, UNLESS egw_vfs::$user changes!
*
* We have to clear the symlink cache before AND after calling the backend,
* because auf traversal rights may be different when egw_vfs::$user changes!
*
* @param string $path='/' path of backend, whos cache to clear
*/
static function clearstatcache($path='/')
{
//error_log(__METHOD__."('$path')");
self::$symlink_cache = array();
self::_call_on_backend('clearstatcache', array($path), true, 0);
self::$symlink_cache = array();
}
/**
* This method is called in response to readdir().
*