forked from extern/egroupware
252 lines
7.2 KiB
PHP
252 lines
7.2 KiB
PHP
<?php
|
|
/**
|
|
* EGroupware API: VFS - Trait to store user / account_id in stream context
|
|
*
|
|
* @link https://www.egroupware.org
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package api
|
|
* @subpackage vfs
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
* @copyright (c) 2020 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
*/
|
|
|
|
namespace EGroupware\Api\Vfs;
|
|
|
|
use EGroupware\Api\Vfs;
|
|
use EGroupware\Api;
|
|
|
|
/**
|
|
* Trait to store user / account_id in stream context
|
|
*
|
|
* Used by Vfs and SqlFS stream-wrapper.
|
|
*
|
|
* @property int $user user / account_id stored in context
|
|
*/
|
|
trait UserContextTrait
|
|
{
|
|
/**
|
|
* optional context param when opening the stream, null if no context passed
|
|
*
|
|
* @var resource
|
|
*/
|
|
public $context;
|
|
|
|
/**
|
|
* Contructor to set context/user incl. from user in url or passed in context
|
|
*
|
|
* @param resource|string|null $url_or_context url with user or context to set
|
|
*/
|
|
public function __construct($url_or_context=null)
|
|
{
|
|
if (is_resource($url_or_context))
|
|
{
|
|
$this->context = $url_or_context;
|
|
}
|
|
else
|
|
{
|
|
if (!isset($this->context)) // PHP set's it before constructor is called!
|
|
{
|
|
$this->context = stream_context_get_default();
|
|
}
|
|
// if context set by PHP contains no user, set user from our default context (Vfs::$user)
|
|
elseif (empty(stream_context_get_options($this->context)[Vfs::SCHEME]['user']))
|
|
{
|
|
stream_context_set_option($this->context, stream_context_get_options(stream_context_get_default()));
|
|
}
|
|
|
|
if (is_string($url_or_context))
|
|
{
|
|
$this->check_set_context($url_or_context);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if we have an url with a user --> set it as context
|
|
*
|
|
* @param $url
|
|
*/
|
|
protected function check_set_context($url)
|
|
{
|
|
if ($url[0] !== '/' && ($account_lid = Vfs::parse_url($url, PHP_URL_USER)))
|
|
{
|
|
$this->user = $account_lid;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 string $path path
|
|
* @param int $check mode to check: one or more or'ed together of: 4 = Vfs::READABLE,
|
|
* 2 = Vfs::WRITABLE, 1 = Vfs::EXECUTABLE
|
|
* @param array|boolean $stat =null stat array or false, to not query it again
|
|
* @return boolean
|
|
*/
|
|
function check_access($path, $check, $stat=null)
|
|
{
|
|
if (Vfs::$is_root)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// throw exception if stat array is used insead of path, can be removed soon
|
|
if (is_array($path))
|
|
{
|
|
throw new Api\Exception\WrongParameter('path has to be string, use check_access($path,$check,$stat=null)!');
|
|
}
|
|
|
|
// if we have no $stat, delegate whole check to vfs stream-wrapper to correctly deal with shares / effective user-ids
|
|
if (is_null($stat))
|
|
{
|
|
$stat = $this->url_stat($path, 0);
|
|
}
|
|
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check)");
|
|
|
|
if (!$stat)
|
|
{
|
|
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) no stat array!");
|
|
return false; // file not found
|
|
}
|
|
|
|
// only vfs stream-wrapper sets $stat['url'], use given url instead
|
|
if (!isset($stat['url']) && $path[0] !== '/')
|
|
{
|
|
$stat['url'] = $path;
|
|
}
|
|
|
|
// if we check writable and have a readonly mount --> return false, as backends dont know about r/o url parameter
|
|
if ($check == Vfs::WRITABLE && Vfs\StreamWrapper::url_is_readonly($stat['url']))
|
|
{
|
|
//error_log(__METHOD__."(path=$path, check=writable, ...) failed because mount is readonly");
|
|
return false;
|
|
}
|
|
|
|
// check if we use an EGroupwre stream wrapper, or a stock php one
|
|
// if it's not an EGroupware one, we can NOT use uid, gid and mode!
|
|
if (($scheme = Vfs::parse_url($stat['url'], PHP_URL_SCHEME)) && !(class_exists(Vfs::scheme2class($scheme))))
|
|
{
|
|
switch($check)
|
|
{
|
|
case Vfs::READABLE:
|
|
return is_readable($stat['url']);
|
|
case Vfs::WRITABLE:
|
|
return is_writable($stat['url']);
|
|
case Vfs::EXECUTABLE:
|
|
return is_executable($stat['url']);
|
|
}
|
|
}
|
|
|
|
// check if other rights grant access
|
|
if (($stat['mode'] & $check) == $check)
|
|
{
|
|
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via other rights!");
|
|
return true;
|
|
}
|
|
|
|
// check if there's owner access and we are the owner
|
|
if (($stat['mode'] & ($check << 6)) == ($check << 6) && $stat['uid'] && $stat['uid'] == $this->user)
|
|
{
|
|
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via owner rights!");
|
|
return true;
|
|
}
|
|
// check if there's a group access and we have the right membership
|
|
if (($stat['mode'] & ($check << 3)) == ($check << 3) && $stat['gid'])
|
|
{
|
|
if (($memberships = Api\Accounts::getInstance()->memberships($this->user, true)) && in_array(-abs($stat['gid']), $memberships))
|
|
{
|
|
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) access via group rights!");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// check extended acls (only if path given)
|
|
$ret = method_exists($this, 'check_extended_acl') && $path && $this->check_extended_acl($stat['url'] ?? $path, $check);
|
|
|
|
//error_log(__METHOD__."(path=$path||stat[name]={$stat['name']},stat[mode]=".sprintf('%o',$stat['mode']).",$check) ".($ret ? 'backend extended acl granted access.' : 'no access!!!'));
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @return mixed|null
|
|
*/
|
|
public function __get($name)
|
|
{
|
|
switch($name)
|
|
{
|
|
case 'user':
|
|
return $this->context ? stream_context_get_options($this->context)[Vfs::SCHEME]['user'] : Vfs::$user;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @param mixed $value
|
|
*/
|
|
public function __set($name, $value)
|
|
{
|
|
switch($name)
|
|
{
|
|
case 'user':
|
|
if (!is_int($value) && is_string($value) && !is_numeric($value))
|
|
{
|
|
$value = Api\Accounts::getInstance()->name2id($value);
|
|
}
|
|
if ($value)
|
|
{
|
|
$options = [
|
|
Vfs::SCHEME => ['user' => (int)$value]
|
|
];
|
|
// do NOT overwrite default context
|
|
if ($this->context && $this->context !== stream_context_get_default())
|
|
{
|
|
stream_context_set_option($this->context, $options);
|
|
}
|
|
else
|
|
{
|
|
$this->context = stream_context_create($options);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->context = stream_context_create();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user context for given url, eg. to use with regular stream functions
|
|
*
|
|
* @param string $url
|
|
* @param array $extra =[] addtional context options
|
|
* @return resource context with user, plus optional extra options
|
|
*/
|
|
public static function userContext($url, array $extra=[])
|
|
{
|
|
if (($user = Vfs::parse_url($url, PHP_URL_USER)) &&
|
|
($account_id = Api\Accounts::getInstance()->name2id($user)) &&
|
|
($account_id != Vfs::$user) || $extra) // never set extra options on default context!
|
|
{
|
|
$context = stream_context_create(array_merge_recursive([Vfs::SCHEME => ['user' => (int)$account_id ?: Vfs::$user]], $extra));
|
|
}
|
|
else
|
|
{
|
|
$context = stream_context_get_default();
|
|
}
|
|
return $context;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @return bool
|
|
*/
|
|
public function __isset($name)
|
|
{
|
|
return $this->__get($name) !== null;
|
|
}
|
|
}
|