From 223455b7aaacca7de7f7f22f6c283dba6108b4cf Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 13 Nov 2014 17:31:36 +0000 Subject: [PATCH] first step for new file-sharing feature --- phpgwapi/inc/class.egw_sharing.inc.php | 113 +++++++++++++++++++++++++ phpgwapi/inc/class.egw_vfs.inc.php | 7 +- phpgwapi/setup/setup.inc.php | 4 +- phpgwapi/setup/tables_current.inc.php | 18 ++++ phpgwapi/setup/tables_update.inc.php | 25 ++++++ share.php | 25 ++++++ 6 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 phpgwapi/inc/class.egw_sharing.inc.php create mode 100644 share.php diff --git a/phpgwapi/inc/class.egw_sharing.inc.php b/phpgwapi/inc/class.egw_sharing.inc.php new file mode 100644 index 0000000000..1933ac4be9 --- /dev/null +++ b/phpgwapi/inc/class.egw_sharing.inc.php @@ -0,0 +1,113 @@ + + * @copyright (c) 2014 by Ralf Becker + * @version $Id$ + */ + +/** + * VFS sharing + * + * Token generation uses openssl_random_pseudo_bytes, if available, otherwise + * mt_rand based auth::randomstring is used. + */ +class egw_sharing +{ + /** + * Length of base64 encoded token (real length is only 3/4 of it) + */ + const TOKEN_LENGTH = 64; + + /** + * Name of table used for storing tokens + */ + const TABLE = 'egw_sharing'; + + /** + * Reference to global db object + * + * @var egw_db + */ + protected $db; + + /** + * Constructor + */ + public function __construct() + { + $this->db = $GLOBALS['egw']->db; + } + + /** + * Server a request on a share specified in REQUEST_URI + */ + public function ServeRequest() + { + // WebDAV has no concept of a query string and clients (including cadaver) + // seem to pass '?' unencoded, so we need to extract the path info out + // of the request URI ourselves + // if request URI contains a full url, remove schema and domain + $matches = null; + if (preg_match('|^https?://[^/]+(/.*)$|', $path_info=$_SERVER['REQUEST_URI'], $matches)) + { + $path_info = $matches[1]; + } + $path_info = substr($path_info, strlen($_SERVER['SCRIPT_NAME'])); + list(, $token/*, $path*/) = explode('/', $path_info, 3); + + if (empty($token) || !($share = $this->db->select(self::TABLE, '*', array( + 'share_token' => $token, + '(share_expires IS NULL OR share_expires > '.$this->db->quote(time(), 'date').')', + ), __LINE__, __FILE__)->fetch())) + { + sleep(1); + $status = '404 Not Found'; + header("HTTP/1.1 $status"); + header("X-WebDAV-Status: $status", true); + echo "Requested resource '".htmlspecialchars($path_info)."' does NOT exist!\n"; + common::egw_exit(); + } + $share['resolve_url'] = egw_vfs::resolve_url($share['share_path']); + //_debug_array($share); + + // arrange vfs to only contain shared url + egw_vfs::$is_root = true; + if (!egw_vfs::mount($share['resolve_url'], '/', false, false, true)) + { + sleep(1); + $status = '404 Not Found'; + header("HTTP/1.1 $status"); + header("X-WebDAV-Status: $status", true); + echo "Requested resource '".htmlspecialchars($path_info)."' does NOT exist!\n"; + common::egw_exit(); + } + egw_vfs::$is_root = false; + egw_vfs::$user = $GLOBALS['egw_info']['user']['account_id'] = $share['share_owner']; + egw_vfs::clearstatcache(); + // ToDo: password and write protection + + //$GLOBALS['egw']->session->commit_session(); + $webdav_server = new vfs_webdav_server(); + $webdav_server->ServeRequest('/'.$token); + } + + /** + * Generate a new token + * + * @return string + */ + public static function token() + { + // generate random token (using oppenssl if available otherwise mt_rand based auth::randomstring) + $token = function_exists('openssl_random_pseudo_bytes') ? + base64_encode(openssl_random_pseudo_bytes(3*self::TOKEN_LENGTH/4)) : + auth::randomstring(self::TOKEN_LENGTH); + + return $token; + } +} \ No newline at end of file diff --git a/phpgwapi/inc/class.egw_vfs.inc.php b/phpgwapi/inc/class.egw_vfs.inc.php index a2793fde20..c7a49b23ed 100644 --- a/phpgwapi/inc/class.egw_vfs.inc.php +++ b/phpgwapi/inc/class.egw_vfs.inc.php @@ -304,9 +304,10 @@ class egw_vfs extends vfs_stream_wrapper * @param boolean $check_url=null check if url is an existing directory, before mounting it * default null only checks if url does not contain a $ as used in $user or $pass * @param boolean $persitent_mount=true create a persitent mount, or only a temprary for current request + * @param boolean $clear_fstab =false true clear current fstab, false (default) only add given mount * @return array|boolean array with fstab, if called without parameter or true on successful mount */ - static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true) + static function mount($url=null,$path=null,$check_url=null,$persitent_mount=true,$clear_fstab=false) { if (is_null($check_url)) $check_url = strpos($url,'$') === false; @@ -329,6 +330,10 @@ class egw_vfs extends vfs_stream_wrapper if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') permission denied, you are NOT root!'); return false; // only root can mount } + if ($clear_fstab) + { + self::$fstab = array(); + } if (isset(self::$fstab[$path]) && self::$fstab[$path] === $url) { if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.'); diff --git a/phpgwapi/setup/setup.inc.php b/phpgwapi/setup/setup.inc.php index 9a270c5993..e2bd98ce27 100755 --- a/phpgwapi/setup/setup.inc.php +++ b/phpgwapi/setup/setup.inc.php @@ -12,7 +12,7 @@ /* Basic information about this app */ $setup_info['phpgwapi']['name'] = 'phpgwapi'; $setup_info['phpgwapi']['title'] = 'EGroupware API'; -$setup_info['phpgwapi']['version'] = '14.1'; +$setup_info['phpgwapi']['version'] = '14.1.900'; $setup_info['phpgwapi']['versions']['current_header'] = '1.29'; $setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['app_order'] = 1; @@ -49,6 +49,7 @@ $setup_info['phpgwapi']['tables'][] = 'egw_cat2entry'; $setup_info['phpgwapi']['tables'][] = 'egw_locks'; $setup_info['phpgwapi']['tables'][] = 'egw_sqlfs_props'; $setup_info['phpgwapi']['tables'][] = 'egw_customfields'; +$setup_info['phpgwapi']['tables'][] = 'egw_sharing'; // hooks used by vfs_home_hooks to manage user- and group-directories for the new stream based VFS $setup_info['phpgwapi']['hooks']['addaccount'] = 'phpgwapi.vfs_home_hooks.addAccount'; @@ -78,3 +79,4 @@ $setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = arra $setup_info['groupdav']['license'] = 'GPL'; $setup_info['groupdav']['hooks']['preferences'] = 'groupdav_hooks::menus'; $setup_info['groupdav']['hooks']['settings'] = 'groupdav_hooks::settings'; + diff --git a/phpgwapi/setup/tables_current.inc.php b/phpgwapi/setup/tables_current.inc.php index 522be9b304..e9fda853d1 100644 --- a/phpgwapi/setup/tables_current.inc.php +++ b/phpgwapi/setup/tables_current.inc.php @@ -450,5 +450,23 @@ $phpgw_baseline = array( 'fk' => array(), 'ix' => array(array('cf_app','cf_order')), 'uc' => array(array('cf_app','cf_name')) + ), + 'egw_sharing' => array( + 'fd' => array( + 'share_id' => array('type' => 'auto','nullable' => False,'comment' => 'auto-id'), + 'share_token' => array('type' => 'varchar','precision' => '64','nullable' => False,'comment' => 'secure token'), + 'share_path' => array('type' => 'varchar','precision' => '255','nullable' => False,'comment' => 'path to share'), + 'share_owner' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => 'owner of share'), + 'share_expires' => array('type' => 'date','comment' => 'expire date of share'), + 'share_writable' => array('type' => 'int','precision' => '1','nullable' => False,'default' => '0','comment' => '0=readable, 1=writable'), + 'share_with' => array('type' => 'varchar','precision' => '4096','comment' => 'email addresses, comma seperated'), + 'share_passwd' => array('type' => 'varchar','precision' => '128','comment' => 'optional password-hash'), + 'share_created' => array('type' => 'timestamp','nullable' => False,'comment' => 'creation date'), + 'share_last_accessed' => array('type' => 'timestamp','comment' => 'last access of share') + ), + 'pk' => array('share_id'), + 'fk' => array(), + 'ix' => array(), + 'uc' => array('share_token') ) ); diff --git a/phpgwapi/setup/tables_update.inc.php b/phpgwapi/setup/tables_update.inc.php index 3edb852b15..4e029fc801 100644 --- a/phpgwapi/setup/tables_update.inc.php +++ b/phpgwapi/setup/tables_update.inc.php @@ -21,3 +21,28 @@ include('tables_update_1_2.inc.php'); include('tables_update_1_4.inc.php'); include('tables_update_1_6.inc.php'); include('tables_update_1_8.inc.php'); + +function phpgwapi_upgrade14_1() +{ + $GLOBALS['egw_setup']->oProc->CreateTable('egw_sharing',array( + 'fd' => array( + 'share_id' => array('type' => 'auto','nullable' => False,'comment' => 'auto-id'), + 'share_token' => array('type' => 'varchar','precision' => '64','nullable' => False,'comment' => 'secure token'), + 'share_path' => array('type' => 'varchar','precision' => '255','nullable' => False,'comment' => 'path to share'), + 'share_owner' => array('type' => 'int','meta' => 'user','precision' => '4','nullable' => False,'comment' => 'owner of share'), + 'share_expires' => array('type' => 'date','comment' => 'expire date of share'), + 'share_writable' => array('type' => 'int','precision' => '1','nullable' => False,'default' => '0','comment' => '0=readable, 1=writable'), + 'share_with' => array('type' => 'varchar','precision' => '4096','comment' => 'email addresses, comma seperated'), + 'share_passwd' => array('type' => 'varchar','precision' => '128','comment' => 'optional password-hash'), + 'share_created' => array('type' => 'timestamp','nullable' => False,'comment' => 'creation date'), + 'share_last_accessed' => array('type' => 'timestamp','comment' => 'last access of share') + ), + 'pk' => array('share_id'), + 'fk' => array(), + 'ix' => array(), + 'uc' => array('share_token') + )); + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '14.1.900'; +} + diff --git a/share.php b/share.php new file mode 100644 index 0000000000..558f8d79ec --- /dev/null +++ b/share.php @@ -0,0 +1,25 @@ + + * @copyright (c) 2014 by Ralf Becker + * @version $Id$ + */ + +$GLOBALS['egw_info'] = array( + 'flags' => array( + 'disable_Template_class' => True, + 'noheader' => True, + 'currentapp' => 'login', + 'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm) + ) +); + +include('./header.inc.php'); + +$sharing = new egw_sharing(); +$sharing->ServeRequest(); \ No newline at end of file