moving egw_digest_auth, vfs_webdav_server and egw_sharing to new api

This commit is contained in:
Ralf Becker 2016-03-20 16:19:53 +00:00
parent 3692f01100
commit 67cb60b972
18 changed files with 477 additions and 420 deletions

View File

@ -0,0 +1,274 @@
<?php
/**
* EGroupware API: Basic and Digest Auth
*
* For Apache FCGI you need the following rewrite rule:
*
* RewriteEngine on
* RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
*
* Otherwise authentication request will be send over and over again, as password is NOT available to PHP!
* (This makes authentication details available in PHP as $_SERVER['REDIRECT_HTTP_AUTHORIZATION']
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage header
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2010-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
namespace EGroupware\Api\Header;
use EGroupware\Api;
/**
* Class to authenticate via basic or digest auth
*
* The more secure digest auth requires:
* a) cleartext passwords in SQL table
* b) md5 hashes of username, realm, password stored somewhere (NOT yet implemented)
* Otherwise digest auth is not possible and therefore not offered to the client.
*
* Usage example:
*
* $GLOBALS['egw_info']['flags'] = array(
* 'noheader' => True,
* 'currentapp' => 'someapp',
* 'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
* 'autocreate_session_callback' => 'EGroupware\\Api\\Header\\Authenticate::autocreate_session_callback',
* 'auth_realm' => 'EGroupware',
* );
* include(dirname(__FILE__).'/header.inc.php');
*
* @link http://www.php.net/manual/en/features.http-auth.php
* @ToDo check if we have to check if returned nonce matches our challange (not done in above link, but why would it be there)
* @link http://en.wikipedia.org/wiki/Digest_access_authentication
* @link http://tools.ietf.org/html/rfc2617
*
* Commented out is accept-charset parameter from (seems not supported by any client I tested with)
* @link https://tools.ietf.org/id/draft-reschke-basicauth-enc-06.html
*
* Implemented support for clients sending credentials in iso-8859-1 instead of our utf-8:
* - Firefox 19.0
* - Thunderbird 17.0.3 with Lightning 1.8
* - IE 8
* - Netdrive
* (Chrome 24 or Safari 6 sends credentials in charset of webpage.)
*/
class Authenticate
{
/**
* Log to error_log:
* 0 = dont
* 1 = no cleartext passwords
* 2 = all
*/
const ERROR_LOG = 0;
/**
* Callback to be used to create session via header include authenticated via basic or digest auth
*
* @param array $account NOT used!
* @return string valid session-id or does NOT return at all!
*/
static public function autocreate_session_callback(&$account)
{
unset($account); // not used, but required by function signature
if (self::ERROR_LOG)
{
$pw = self::ERROR_LOG > 1 ? $_SERVER['PHP_AUTH_PW'] : '**********';
error_log(__METHOD__.'() PHP_AUTH_USER='.array2string($_SERVER['PHP_AUTH_USER']).', PHP_AUTH_PW='.array2string($pw).', PHP_AUTH_DIGEST='.array2string($_SERVER['PHP_AUTH_DIGEST']));
}
$realm = $GLOBALS['egw_info']['flags']['auth_realm'];
if (empty($realm)) $realm = 'EGroupware';
$username = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW'];
// Support for basic auth when using PHP CGI (what about digest auth?)
if (!isset($username) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'],'Basic ') === 0)
{
$hash = base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'],6));
if (strpos($hash, ':') !== false)
{
list($username, $password) = explode(':', $hash, 2);
}
}
elseif (isset($_SERVER['PHP_AUTH_DIGEST']) && !self::is_valid($realm,$_SERVER['PHP_AUTH_DIGEST'],$username,$password))
{
unset($password);
}
// if given password contains non-ascii chars AND we can not authenticate with it
if (isset($username) && isset($password) &&
(preg_match('/[^\x20-\x7F]/', $password) || strpos($password, '\\x') !== false) &&
!$GLOBALS['egw']->auth->authenticate($username, $password, 'text'))
{
self::decode_password($password);
}
// create session without session cookie (session->create(..., true), as we use pseudo sessionid from credentials
if (!isset($username) || !($sessionid = $GLOBALS['egw']->session->create($username, $password, 'text', true)))
{
// if the session class gives a reason why the login failed --> append it to the REALM
if ($GLOBALS['egw']->session->reason) $realm .= ': '.$GLOBALS['egw']->session->reason;
header('WWW-Authenticate: Basic realm="'.$realm.'"');// draft-reschke-basicauth-enc-06 adds, accept-charset="'.translation::charset().'"');
self::digest_header($realm);
header('HTTP/1.1 401 Unauthorized');
header('X-WebDAV-Status: 401 Unauthorized', true);
echo "<html>\n<head>\n<title>401 Unauthorized</title>\n<body>\nAuthorization failed.\n</body>\n</html>\n";
exit;
}
return $sessionid;
}
/**
* Decode password containing non-ascii chars
*
* @param string &$password
* @return boolean true if conversation happend, false if there was no need for a conversation
*/
public static function decode_password(&$password)
{
// if given password contains non-ascii chars AND we can not authenticate with it
if (preg_match('/[^\x20-\x7F]/', $password) || strpos($password, '\\x') !== false)
{
// replace \x encoded non-ascii chars in password, as they are used eg. by Thunderbird for German umlauts
if (strpos($password, '\\x') !== false)
{
$password = preg_replace_callback('/\\\\x([0-9A-F]{2})/i', function($matches){
return chr(hexdec($matches[1]));
}, $password);
}
// try translating the password from iso-8859-1 to utf-8
$password = Api\Translation::convert($password, 'iso-8859-1');
//error_log(__METHOD__."() Fixed non-ascii password of user '$username' from '$_SERVER[PHP_AUTH_PW]' to '$password'");
return true;
}
return false;
}
/**
* Check if digest auth is available for a given realm (and user): do we use cleartext passwords
*
* If no user is given, check is NOT authoritative, as we can only check if cleartext passwords are generally used
*
* @param string $realm
* @param string $username =null username or null to only check if we auth agains sql and use plaintext passwords
* @param string &$user_pw =null stored cleartext password, if $username given AND function returns true
* @return boolean true if digest auth is available, false otherwise
*/
static public function digest_auth_available($realm,$username=null,&$user_pw=null)
{
// we currently require plaintext passwords!
if (!($GLOBALS['egw_info']['server']['auth_type'] == 'sql' && $GLOBALS['egw_info']['server']['sql_encryption_type'] == 'plain') ||
$GLOBALS['egw_info']['server']['auth_type'] == 'ldap' && $GLOBALS['egw_info']['server']['ldap_encryption_type'] == 'plain')
{
if (self::ERROR_LOG) error_log(__METHOD__."('$username') return false (no plaintext passwords used)");
return false; // no plain-text passwords used
}
// check for specific user, if given
if (!is_null($username) && !(($user_pw = $GLOBALS['egw']->accounts->id2name($username,'account_pwd','u')) ||
$GLOBALS['egw_info']['server']['auth_type'] == 'sql' && substr($user_pw,0,7) != '{PLAIN}'))
{
unset($user_pw);
if (self::ERROR_LOG) error_log(__METHOD__."('$realm','$username') return false (unknown user or NO plaintext password for user)");
return false; // user does NOT exist, or has no plaintext passwords (ldap server requires real root_dn or special ACL!)
}
if (substr($user_pw,0,7) == '{PLAIN}') $user_pw = substr($user_pw,7);
if (self::ERROR_LOG)
{
$pw = self::ERROR_LOG > 1 ? $user_pw : '**********';
error_log(__METHOD__."('$realm','$username','$pw') return true");
}
return true;
}
/**
* Send header offering digest auth, if it's generally available
*
* @param string $realm
* @param string &$nonce=null on return
*/
static public function digest_header($realm,&$nonce=null)
{
if (self::digest_auth_available($realm))
{
$nonce = uniqid();
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.$nonce.'",opaque="'.md5($realm).'"');
if (self::ERROR_LOG) error_log(__METHOD__."() offering digest auth for realm '$realm' using nonce='$nonce'");
}
}
/**
* Check digest
*
* @param string $realm
* @param string $auth_digest =null default to $_SERVER['PHP_AUTH_DIGEST']
* @param string &$username on return username
* @param string &$password on return cleartext password
* @return boolean true if digest is correct, false otherwise
*/
static public function is_valid($realm,$auth_digest=null,&$username=null,&$password=null)
{
if (is_null($auth_digest)) $auth_digest = $_SERVER['PHP_AUTH_DIGEST'];
$data = self::parse_digest($auth_digest);
if (!$data || !($A1 = self::get_digest_A1($realm,$username=$data['username'],$password=null)))
{
error_log(__METHOD__."('$realm','$auth_digest','$username') returning FALSE");
return false;
}
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if (self::ERROR_LOG) error_log(__METHOD__."('$realm','$auth_digest','$username') response='$data[response]', valid_response='$valid_response' returning ".array2string($data['response'] === $valid_response));
return $data['response'] === $valid_response;
}
/**
* Calculate the A1 digest hash
*
* @param string $realm
* @param string $username
* @param string &$password=null password to use or if null, on return stored password
* @return string|boolean false if $password not given and can NOT be read
*/
static private function get_digest_A1($realm,$username,&$password=null)
{
$user_pw = null;
if (empty($username) || empty($realm) || !self::digest_auth_available($realm,$username,$user_pw))
{
return false;
}
if (is_null($password)) $password = $user_pw;
$A1 = md5($username . ':' . $realm . ':' . $password);
if (self::ERROR_LOG > 1) error_log(__METHOD__."('$realm','$username','$password') returning ".array2string($A1));
return $A1;
}
/**
* Parse the http auth header
*/
static public function parse_digest($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
$matches = null;
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m)
{
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
//error_log(__METHOD__."('$txt') returning ".array2string($needed_parts ? false : $data));
return $needed_parts ? false : $data;
}
}

View File

@ -24,7 +24,6 @@ namespace EGroupware\Api;
// explicitly reference classes still in phpgwapi
use egw_mailer;
use egw_digest_auth; // egw_digest_auth::parse_digest
/**
* Create, verifies or destroys an EGroupware session
@ -831,7 +830,7 @@ class Session
{
// we generate a pseudo-sessionid from the digest username, realm and nounce
// can't use full $_SERVER['PHP_AUTH_DIGEST'], as it changes (contains eg. the url)
$data = egw_digest_auth::parse_digest($_SERVER['PHP_AUTH_DIGEST']);
$data = Header\Authenticate::parse_digest($_SERVER['PHP_AUTH_DIGEST']);
$sessionid = md5($data['username'].':'.$data['realm'].':'.$data['nonce'].':'.$_SERVER['HTTP_HOST'].
EGW_SERVER_ROOT.':'.self::getuser_ip().':'.filemtime(EGW_SERVER_ROOT.'/phpgwapi/setup/setup.inc.php').
':'.$_SERVER['HTTP_USER_AGENT']);

View File

@ -5,16 +5,30 @@
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage Vfs
* @author Ralf Becker <rb@stylite.de>
* @copyright (c) 2014/15 by Ralf Becker <rb@stylite.de>
* @copyright (c) 2014-16 by Ralf Becker <rb@stylite.de>
* @version $Id$
*/
namespace EGroupware\Api\Vfs;
use EGroupware\Api;
use EGroupware\Api\Vfs;
// explicitly list old, not yet ported api classes
use common; // egw_exist
use egw_framework;
use asyncservice;
use egw; // link
use filemanager_ui;
/**
* VFS sharing
*
* Token generation uses openssl_random_pseudo_bytes, if available, otherwise
* mt_rand based auth::randomstring is used.
* mt_rand based Api\Auth::randomstring is used.
*
* Existing user sessions are kept whenever possible by an additional mount into regular VFS:
* - share owner is current user (no problems with rights, they simply match)
@ -26,7 +40,7 @@
* @todo handle mounts inside shared directory (they get currently lost)
* @todo handle absolute symlinks (wont work as we use share as root)
*/
class egw_sharing
class Sharing
{
/**
* Length of base64 encoded token (real length is only 3/4 of it)
@ -184,9 +198,9 @@ class egw_sharing
// check password, if required
if ($share['share_passwd'] && (empty($_SERVER['PHP_AUTH_PW']) ||
!(auth::compare_password($_SERVER['PHP_AUTH_PW'], $share['share_passwd'], 'crypt') ||
egw_digest_auth::decode_password($_SERVER['PHP_AUTH_PW']) &&
auth::compare_password($_SERVER['PHP_AUTH_PW'], $share['share_passwd'], 'crypt'))))
!(Api\Auth::compare_password($_SERVER['PHP_AUTH_PW'], $share['share_passwd'], 'crypt') ||
Api\Header\Authenticate::decode_password($_SERVER['PHP_AUTH_PW']) &&
Api\Auth::compare_password($_SERVER['PHP_AUTH_PW'], $share['share_passwd'], 'crypt'))))
{
$realm = 'EGroupware share '.$share['share_token'];
header('WWW-Authenticate: Basic realm="'.$realm.'"');
@ -201,10 +215,10 @@ class egw_sharing
if (count($GLOBALS['egw_info']['server']['vfs_fstab']) <= 1)
{
unset($GLOBALS['egw_info']['server']['vfs_fstab']); // triggers reset of fstab in mount()
$GLOBALS['egw_info']['server']['vfs_fstab'] = egw_vfs::mount();
egw_vfs::clearstatcache();
$GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount();
Vfs::clearstatcache();
}
$share['resolve_url'] = egw_vfs::resolve_url($share['share_path'], true, true, true, true); // true = fix evtl. contained url parameter
$share['resolve_url'] = Vfs::resolve_url($share['share_path'], true, true, true, true); // true = fix evtl. contained url parameter
// if share not writable append ro=1 to mount url to make it readonly
if (!self::$db->from_bool($share['share_writable']))
{
@ -217,7 +231,7 @@ class egw_sharing
$share['share_root'] = '/'.$share['share_token'];
// if current user is not the share owner, we cant just mount share
if (egw_vfs::$user != $share['share_owner'])
if (Vfs::$user != $share['share_owner'])
{
$keep_session = false;
}
@ -230,12 +244,12 @@ class egw_sharing
);
$share['share_root'] = '/';
egw_vfs::$user = $share['share_owner'];
Vfs::$user = $share['share_owner'];
}
// mounting share
egw_vfs::$is_root = true;
if (!egw_vfs::mount($share['resolve_url'], $share['share_root'], false, false, !$keep_session))
Vfs::$is_root = true;
if (!Vfs::mount($share['resolve_url'], $share['share_root'], false, false, !$keep_session))
{
sleep(1);
$status = '404 Not Found';
@ -244,8 +258,8 @@ class egw_sharing
echo "Requested resource '/".htmlspecialchars($token)."' does NOT exist!\n";
common::egw_exit();
}
egw_vfs::$is_root = false;
egw_vfs::clearstatcache();
Vfs::$is_root = false;
Vfs::clearstatcache();
// update accessed timestamp
self::$db->update(self::TABLE, array(
@ -255,7 +269,7 @@ class egw_sharing
), __LINE__, __FILE__);
// store sharing object in egw object and therefore in session
$GLOBALS['egw']->sharing = new egw_sharing($share);
$GLOBALS['egw']->sharing = new Sharing($share);
// we have a session we want to keep, but share owner is different from current user and we need filemanager UI, or no session
// --> create a new anon session
@ -284,16 +298,16 @@ class egw_sharing
$GLOBALS['egw']->session->commit_session();
}
// need to store new fstab and vfs_user in session to allow GET requests / downloads via WebDAV
$GLOBALS['egw_info']['user']['vfs_user'] = egw_vfs::$user;
$GLOBALS['egw_info']['server']['vfs_fstab'] = egw_vfs::mount();
$GLOBALS['egw_info']['user']['vfs_user'] = Vfs::$user;
$GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount();
// update modified egw and egw_info again in session, if neccessary
if ($keep_session || $sessionid)
{
$_SESSION[egw_session::EGW_INFO_CACHE] = $GLOBALS['egw_info'];
unset($_SESSION[egw_session::EGW_INFO_CACHE]['flags']); // dont save the flags, they change on each request
$_SESSION[Api\Session::EGW_INFO_CACHE] = $GLOBALS['egw_info'];
unset($_SESSION[Api\Session::EGW_INFO_CACHE]['flags']); // dont save the flags, they change on each request
$_SESSION[egw_session::EGW_OBJECT_CACHE] = serialize($GLOBALS['egw']);
$_SESSION[Api\Session::EGW_OBJECT_CACHE] = serialize($GLOBALS['egw']);
}
return $sessionid;
@ -308,9 +322,9 @@ class egw_sharing
*/
public function use_filemanager()
{
return !(!egw_vfs::is_dir($this->share['share_root']) || $_SERVER['REQUEST_METHOD'] != 'GET' ||
return !(!Vfs::is_dir($this->share['share_root']) || $_SERVER['REQUEST_METHOD'] != 'GET' ||
// or unsupported browsers like ie < 10
html::$user_agent == 'msie' && html::$ua_version < 10.0 ||
Api\Header\UserAgent::type() == 'msie' && Api\Header\UserAgent::version() < 10.0 ||
// or if no filemanager installed (WebDAV has own autoindex)
!file_exists(__DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php'));
}
@ -331,13 +345,13 @@ class egw_sharing
if (!$this->use_filemanager())
{
// send a content-disposition header, so browser knows how to name downloaded file
if (!egw_vfs::is_dir($this->share['share_root']))
if (!Vfs::is_dir($this->share['share_root']))
{
html::content_disposition_header(egw_vfs::basename($this->share['share_path']), false);
Api\Header\Content::disposition(Vfs::basename($this->share['share_path']), false);
}
//$GLOBALS['egw']->session->commit_session();
$webdav_server = new vfs_webdav_server();
$webdav_server->ServeRequest(egw_vfs::concat($this->share['share_root'], $this->share['share_token']));
$webdav_server = new Vfs\WebDAV();
$webdav_server->ServeRequest(Vfs::concat($this->share['share_root'], $this->share['share_token']));
return;
}
// run full eTemplate2 UI for directories
@ -346,7 +360,7 @@ class egw_sharing
$_GET['cd'] = 'no';
$GLOBALS['egw_info']['flags']['js_link_registry'] = true;
egw_framework::includeCSS('filemanager', 'sharing');
$ui = new egw_sharing_filemanager();
$ui = new SharingUi();
$ui->index();
}
@ -357,13 +371,13 @@ class egw_sharing
*/
public static function token()
{
// generate random token (using oppenssl if available otherwise mt_rand based auth::randomstring)
// generate random token (using oppenssl if available otherwise mt_rand based Api\Auth::randomstring)
do {
$token = function_exists('openssl_random_pseudo_bytes') ?
base64_encode(openssl_random_pseudo_bytes(3*self::TOKEN_LENGTH/4)) :
auth::randomstring(self::TOKEN_LENGTH);
Api\Auth::randomstring(self::TOKEN_LENGTH);
// base64 can contain chars not allowed in our vfs-urls eg. / or #
} while ($token != egw_vfs::encodePathComponent($token));
} while ($token != Vfs::encodePathComponent($token));
return $token;
}
@ -387,7 +401,7 @@ class egw_sharing
if (empty($name)) $name = $path;
$path2tmp =& egw_cache::getSession(__CLASS__, 'path2tmp');
$path2tmp =& Api\Cache::getSession(__CLASS__, 'path2tmp');
// allow filesystem path only for temp_dir
$temp_dir = $GLOBALS['egw_info']['server']['temp_dir'].'/';
@ -402,13 +416,13 @@ class egw_sharing
{
$path = 'vfs://default'.($path[0] == '/' ? '' : '/').$path;
}
$vfs_path = egw_vfs::parse_url($path, PHP_URL_PATH);
$exists = egw_vfs::file_exists($vfs_path) && egw_vfs::is_readable($vfs_path);
$vfs_path = Vfs::parse_url($path, PHP_URL_PATH);
$exists = Vfs::file_exists($vfs_path) && Vfs::is_readable($vfs_path);
}
// check if file exists and is readable
if (!$exists)
{
throw new egw_exception_not_found("'$path' NOT found!");
throw new Api\Exception\NotFound("'$path' NOT found!");
}
// check if file has been shared before, with identical attributes
if (($mode != self::LINK || isset($path2tmp[$path])) &&
@ -447,29 +461,29 @@ class egw_sharing
if ($mode == 'link')
{
$user_tmp = '/home/'.$GLOBALS['egw_info']['user']['account_lid'].'/.tmp';
if (!egw_vfs::file_exists($user_tmp) && !egw_vfs::mkdir($user_tmp))
if (!Vfs::file_exists($user_tmp) && !Vfs::mkdir($user_tmp))
{
throw new egw_exception_assertion_failed("Could NOT create temp. directory '$user_tmp'!");
throw new Api\Exception\AssertionFailed("Could NOT create temp. directory '$user_tmp'!");
}
$n = 0;
do {
$tmp_file = egw_vfs::concat($user_tmp, ($n?$n.'.':'').egw_vfs::basename($name));
$tmp_file = Vfs::concat($user_tmp, ($n?$n.'.':'').Vfs::basename($name));
}
while(!(is_dir($path) && egw_vfs::mkdir($tmp_file) ||
!is_dir($path) && (!egw_vfs::file_exists($tmp_file) && ($fp = egw_vfs::fopen($tmp_file, 'x')) ||
while(!(is_dir($path) && Vfs::mkdir($tmp_file) ||
!is_dir($path) && (!Vfs::file_exists($tmp_file) && ($fp = Vfs::fopen($tmp_file, 'x')) ||
// do not copy identical files again to users tmp dir, just re-use them
egw_vfs::file_exists($tmp_file) && egw_vfs::compare(egw_vfs::PREFIX.$tmp_file, $path))) && $n++ < 100);
Vfs::file_exists($tmp_file) && Vfs::compare(Vfs::PREFIX.$tmp_file, $path))) && $n++ < 100);
if ($n >= 100)
{
throw new egw_exception_assertion_failed("Could NOT create temp. file '$tmp_file'!");
throw new Api\Exception\AssertionFailed("Could NOT create temp. file '$tmp_file'!");
}
if ($fp) fclose($fp);
if (is_dir($path) && !egw_vfs::copy_files(array($path), $tmp_file) ||
!is_dir($path) && !copy($path, egw_vfs::PREFIX.$tmp_file))
if (is_dir($path) && !Vfs::copy_files(array($path), $tmp_file) ||
!is_dir($path) && !copy($path, Vfs::PREFIX.$tmp_file))
{
throw new egw_exception_assertion_failed("Could NOT create temp. file '$tmp_file'!");
throw new Api\Exception\AssertionFailed("Could NOT create temp. file '$tmp_file'!");
}
// store temp. path in session, to be able to add more recipients
$path2tmp[$path] = $tmp_file;
@ -490,7 +504,7 @@ class egw_sharing
try {
self::$db->insert(self::TABLE, $share = array(
'share_token' => self::token(),
'share_path' => egw_vfs::parse_url($path, PHP_URL_PATH),
'share_path' => Vfs::parse_url($path, PHP_URL_PATH),
'share_owner' => $GLOBALS['egw_info']['user']['account_id'],
'share_with' => implode(',', (array)$recipients),
'share_created' => time(),
@ -499,7 +513,7 @@ class egw_sharing
$share['share_id'] = self::$db->get_last_insert_id(self::TABLE, 'share_id');
break;
}
catch(egw_exception_db $e) {
catch(Api\Db\Exception $e) {
if ($i++ > 3) throw $e;
unset($e);
}
@ -509,9 +523,9 @@ class egw_sharing
}
/**
* so_sql instance for egw_sharing table
* Api\Storage\Base instance for egw_sharing table
*
* @var so_sql
* @var Api\Storage\Base
*/
protected static $so;
@ -522,7 +536,7 @@ class egw_sharing
{
if (!isset(self::$so))
{
self::$so = new so_sql('phpgwapi', self::TABLE, null, '', true);
self::$so = new Api\Storage\Base('phpgwapi', self::TABLE, null, '', true);
self::$so->set_times('string');
}
return self::$so;
@ -567,7 +581,7 @@ class egw_sharing
// if not delete them
foreach($tmp_paths as $path)
{
egw_vfs::remove($path);
Vfs::remove($path);
}
}
return $deleted;
@ -586,7 +600,7 @@ class egw_sharing
public static function tmp_cleanup()
{
if (!isset(self::$db)) self::$db = $GLOBALS['egw']->db;
egw_vfs::$is_root = true;
Vfs::$is_root = true;
try {
$cols = array(
@ -607,7 +621,7 @@ class egw_sharing
"share_path LIKE '/home/%/.tmp/%'",
), __LINE__, __FILE__, false, 'GROUP BY share_path '.$having) as $row)
{
egw_vfs::remove($row['share_path']);
Vfs::remove($row['share_path']);
if ($group_concat)
{
@ -629,10 +643,10 @@ class egw_sharing
}
}
}
catch (Exception $e) {
catch (\Exception $e) {
unset($e);
}
egw_vfs::$is_root = false;
Vfs::$is_root = false;
}
/**
@ -661,7 +675,7 @@ if (file_exists(__DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php'))
{
require_once __DIR__.'/../../filemanager/inc/class.filemanager_ui.inc.php';
class egw_sharing_filemanager extends filemanager_ui
class SharingUi extends filemanager_ui
{
/**
* Get the configured start directory for the current user

View File

@ -7,24 +7,30 @@
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @subpackage vfs
* @subpackage webdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Hartmut Holzgraefe <hartmut@php.net> original HTTP/WebDAV/Server/Filesystem class, of which some code is used
* @version $Id$
*/
if (strpos(ini_get('include_path'), EGW_API_INC) === false)
{
ini_set('include_path', EGW_API_INC.PATH_SEPARATOR.ini_get('include_path'));
}
require_once('HTTP/WebDAV/Server/Filesystem.php');
namespace EGroupware\Api\Vfs;
require_once dirname(__DIR__).'/WebDAV/Server/Filesystem.php';
use HTTP_WebDAV_Server_Filesystem;
use HTTP_WebDAV_Server;
use EGroupware\Api\Vfs;
use EGroupware\Api;
// old, not yet ported api classes
use common; // egw_exit
/**
* FileManger - WebDAV access using the new stream wrapper VFS interface
*
* Using modified PEAR HTTP/WebDAV/Server/Filesystem class in API dir
*/
class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
class WebDAV extends HTTP_WebDAV_Server_Filesystem
{
/**
* Realm of eGW's WebDAV server
@ -39,7 +45,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
*
* @var string
*/
var $base = egw_vfs::PREFIX;
var $base = Vfs::PREFIX;
/**
* Debug level: 0 = nothing, 1 = function calls, 2 = more info, eg. complete $_SERVER array
@ -90,11 +96,11 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
{
// recursive delete the directory
try {
$deleted = egw_vfs::remove($options['path']);
$deleted = Vfs::remove($options['path']);
$ret = !empty($deleted[$options['path']]);
//error_log(__METHOD__."() egw_vfs::remove($options[path]) returned ".array2string($deleted)." --> ".array2string($ret));
//error_log(__METHOD__."() Vfs::remove($options[path]) returned ".array2string($deleted)." --> ".array2string($ret));
}
catch (Exception $e) {
catch (\Exception $e) {
return '403 Forbidden: '.$e->getMessage();
}
}
@ -120,7 +126,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
function MKCOL($options)
{
$path = $this->_unslashify($this->base .$options["path"]);
$parent = egw_vfs::dirname($path);
$parent = Vfs::dirname($path);
if (!file_exists($parent)) {
return "409 Conflict";
@ -225,7 +231,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
}
} else {
if (is_dir($source) && $options['depth'] == 'infinity') {
$files = egw_vfs::find($source,array('depth' => true,'url' => true)); // depth=true: return dirs first, url=true: allow urls!
$files = Vfs::find($source,array('depth' => true,'url' => true)); // depth=true: return dirs first, url=true: allow urls!
} else {
$files = array($source);
}
@ -289,41 +295,41 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
$info['props'] = array();
// no special beautified displayname here ...
$info['props'][] = HTTP_WebDAV_Server::mkprop ('displayname', egw_vfs::basename(self::_unslashify($info['path'])));
$info['props'][] = self::mkprop ('displayname', Vfs::basename(self::_unslashify($info['path'])));
// creation and modification time
$info['props'][] = HTTP_WebDAV_Server::mkprop ('creationdate', filectime($fspath));
$info['props'][] = HTTP_WebDAV_Server::mkprop ('getlastmodified', filemtime($fspath));
$info['props'][] = self::mkprop ('creationdate', filectime($fspath));
$info['props'][] = self::mkprop ('getlastmodified', filemtime($fspath));
// Microsoft extensions: last access time and 'hidden' status
$info["props"][] = HTTP_WebDAV_Server::mkprop("lastaccessed", fileatime($fspath));
$info["props"][] = HTTP_WebDAV_Server::mkprop("ishidden", egw_vfs::is_hidden($fspath));
$info["props"][] = self::mkprop("lastaccessed", fileatime($fspath));
$info["props"][] = self::mkprop("ishidden", Vfs::is_hidden($fspath));
// type and size (caller already made sure that path exists)
if (is_dir($fspath)) {
// directory (WebDAV collection)
$info['props'][] = HTTP_WebDAV_Server::mkprop ('resourcetype', array(
HTTP_WebDAV_Server::mkprop('collection', '')));
$info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontenttype', 'httpd/unix-directory');
$info['props'][] = self::mkprop ('resourcetype', array(
self::mkprop('collection', '')));
$info['props'][] = self::mkprop ('getcontenttype', 'httpd/unix-directory');
} else {
// plain file (WebDAV resource)
$info['props'][] = HTTP_WebDAV_Server::mkprop ('resourcetype', '');
if (egw_vfs::is_readable($path)) {
$info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontenttype', egw_vfs::mime_content_type($path));
$info['props'][] = self::mkprop ('resourcetype', '');
if (Vfs::is_readable($path)) {
$info['props'][] = self::mkprop ('getcontenttype', Vfs::mime_content_type($path));
} else {
error_log(__METHOD__."($path) $fspath is not readable!");
$info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontenttype', 'application/x-non-readable');
$info['props'][] = self::mkprop ('getcontenttype', 'application/x-non-readable');
}
$info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontentlength', filesize($fspath));
$info['props'][] = self::mkprop ('getcontentlength', filesize($fspath));
}
// generate etag from inode (sqlfs: fs_id), modification time and size
$stat = stat($fspath);
$info['props'][] = HTTP_WebDAV_Server::mkprop('getetag', '"'.$stat['ino'].':'.$stat['mtime'].':'.$stat['size'].'"');
$info['props'][] = self::mkprop('getetag', '"'.$stat['ino'].':'.$stat['mtime'].':'.$stat['size'].'"');
/* returning the supportedlock property causes Windows DAV provider and Konqueror to not longer work
ToDo: return it only if explicitly requested ($options['props'])
// supportedlock property
$info['props'][] = HTTP_WebDAV_Server::mkprop('supportedlock','
$info['props'][] = self::mkprop('supportedlock','
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:lockscope>
@ -384,13 +390,13 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
$_path = $info['path'];
if (!$n && $info['path'] != '/' && substr($info['path'],-1) == '/') $_path = substr($info['path'],0,-1);
// need to encode path again, as $info['path'] is NOT encoded, but egw_vfs::(stat|propfind) require it
// need to encode path again, as $info['path'] is NOT encoded, but Vfs::(stat|propfind) require it
// otherwise pathes containing url special chars like ? or # will not stat
$path = egw_vfs::encodePath($_path);
$path = Vfs::encodePath($_path);
$path2n[$path] = $n;
// adding some properties used instead of regular DAV times
if (($stat = egw_vfs::stat($path)))
if (($stat = Vfs::stat($path)))
{
$fileprops =& $files['files'][$path2n[$path]]['props'];
foreach(self::$auto_props as $attr => $props)
@ -414,14 +420,14 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
}
}
}
if ($path2n && ($path2props = egw_vfs::propfind(array_keys($path2n),null)))
if ($path2n && ($path2props = Vfs::propfind(array_keys($path2n),null)))
{
foreach($path2props as $path => $props)
{
$fileprops =& $files['files'][$path2n[$path]]['props'];
foreach($props as $prop)
{
if ($prop['ns'] == egw_vfs::DEFAULT_PROP_NAMESPACE && $prop['name'][0] == '#') // eGW's customfields
if ($prop['ns'] == Vfs::DEFAULT_PROP_NAMESPACE && $prop['name'][0] == '#') // eGW's customfields
{
$prop['ns'] .= 'customfields/';
$prop['name'] = substr($prop['name'],1);
@ -437,13 +443,13 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
/**
* Used eg. by get
*
* @todo replace all calls to _mimetype with egw_vfs::mime_content_type()
* @todo replace all calls to _mimetype with Vfs::mime_content_type()
* @param string $path
* @return string
*/
function _mimetype($path)
{
return egw_vfs::mime_content_type($path);
return Vfs::mime_content_type($path);
}
/**
@ -454,7 +460,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
*/
function _is_readable($fspath)
{
return egw_vfs::is_readable($fspath);
return Vfs::is_readable($fspath);
}
/**
@ -465,7 +471,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
*/
function _is_writable($fspath)
{
return egw_vfs::is_writable($fspath);
return Vfs::is_writable($fspath);
}
/**
@ -480,7 +486,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
*/
function PROPPATCH(&$options)
{
$path = translation::convert($options['path'],'utf-8');
$path = Api\Translation::convert($options['path'],'utf-8');
foreach ($options['props'] as $key => $prop) {
$attributes = array();
@ -492,14 +498,14 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
{
case 'srt_modifiedtime':
case 'getlastmodified':
egw_vfs::touch($path,strtotime($prop['val']));
Vfs::touch($path,strtotime($prop['val']));
break;
//case 'srt_creationtime':
// no streamwrapper interface / php function to set the ctime currently
//$attributes['created'] = strtotime($prop['val']);
//break;
default:
if (!egw_vfs::proppatch($path,array($prop))) $options['props'][$key]['status'] = '403 Forbidden';
if (!Vfs::proppatch($path,array($prop))) $options['props'][$key]['status'] = '403 Forbidden';
break;
}
break;
@ -509,7 +515,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
{
// allow netdrive to change the modification time
case 'getlastmodified':
egw_vfs::touch($path,strtotime($prop['val']));
Vfs::touch($path,strtotime($prop['val']));
break;
// not sure why, the filesystem example of the WebDAV class does it ...
default:
@ -522,23 +528,23 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
switch($prop['name'])
{
case 'Win32LastModifiedTime':
egw_vfs::touch($path,strtotime($prop['val']));
Vfs::touch($path,strtotime($prop['val']));
break;
case 'Win32CreationTime': // eg. "Wed, 14 Sep 2011 15:48:26 GMT"
case 'Win32LastAccessTime':
case 'Win32FileAttributes': // not sure what that is, it was always "00000000"
default:
if (!egw_vfs::proppatch($path,array($prop))) $options['props'][$key]['status'] = '403 Forbidden';
if (!Vfs::proppatch($path,array($prop))) $options['props'][$key]['status'] = '403 Forbidden';
break;
}
break;
case egw_vfs::DEFAULT_PROP_NAMESPACE.'customfields/': // eGW's customfields
$prop['ns'] = egw_vfs::DEFAULT_PROP_NAMESPACE;
case Vfs::DEFAULT_PROP_NAMESPACE.'customfields/': // eGW's customfields
$prop['ns'] = Vfs::DEFAULT_PROP_NAMESPACE;
$prop['name'] = '#'.$prop['name'];
// fall through
default:
if (!egw_vfs::proppatch($path,array($prop))) $options['props'][$key]['status'] = '403 Forbidden';
if (!Vfs::proppatch($path,array($prop))) $options['props'][$key]['status'] = '403 Forbidden';
break;
}
if ($this->debug) $props[] = '('.$prop['ns'].')'.$prop['name'].'='.$prop['val'];
@ -570,7 +576,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
// dont know why, but HTTP_WebDAV_Server passes the owner in D:href tags, which get's passed unchanged to checkLock/PROPFIND
// that's wrong according to the standard and cadaver does not show it on discover --> strip_tags removes eventual tags
if (($ret = egw_vfs::lock($options['path'],$options['locktoken'],$options['timeout'],strip_tags($options['owner']),
if (($ret = Vfs::lock($options['path'],$options['locktoken'],$options['timeout'],strip_tags($options['owner']),
$options['scope'],$options['type'],isset($options['update']))) && !isset($options['update']))
{
return $ret ? '200 OK' : '409 Conflict';
@ -587,7 +593,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
function UNLOCK(&$options)
{
if ($this->debug) error_log(__METHOD__.'('.str_replace(array("\n",' '),'',print_r($options,true)).')');
return egw_vfs::unlock($options['path'],$options['token']) ? '204 No Content' : '409 Conflict';
return Vfs::unlock($options['path'],$options['token']) ? '204 No Content' : '409 Conflict';
}
/**
@ -598,7 +604,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
*/
function checkLock($path)
{
return egw_vfs::checkLock($path);
return Vfs::checkLock($path);
}
/**
@ -612,7 +618,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
function GetDir($fspath, &$options)
{
// add a content-type header to overwrite an existing default charset in apache (AddDefaultCharset directiv)
header('Content-type: text/html; charset='.translation::charset());
header('Content-type: text/html; charset='.Api\Translation::charset());
parent::GetDir($fspath, $options);
}
@ -650,7 +656,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
if (($ok = parent::GET($options)))
{
// mitigate risks of serving javascript or css from our domain
html::safe_content_header($options['stream'], $options['path'], $options['mimetype'], $options['size'], false,
Api\Header\Content::safe($options['stream'], $options['path'], $options['mimetype'], $options['size'], false,
$this->force_download, true); // true = do not send content-type and content-length header, but modify values
if (!is_resource($options['stream']))
@ -678,7 +684,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
{
return $ret; // no collection
}
header('Content-type: text/html; charset='.translation::charset());
header('Content-type: text/html; charset='.Api\Translation::charset());
echo "<html>\n<head>\n\t<title>".'EGroupware WebDAV server '.htmlspecialchars($options['path'])."</title>\n";
echo "\t<meta http-equiv='content-type' content='text/html; charset=utf-8' />\n";
echo "\t<style type='text/css'>\n.th { background-color: #e0e0e0; }\n.row_on { background-color: #F1F1F1; vertical-align: top; }\n".
@ -691,7 +697,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
foreach(explode('/',$this->_unslashify($options['path'])) as $n => $name)
{
$path .= ($n != 1 ? '/' : '').$name;
echo html::a_href(htmlspecialchars($name.'/'),$path);
echo Api\Html::a_href(htmlspecialchars($name.'/'),$path);
}
echo "</h1>\n";
@ -739,7 +745,7 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem
}
echo "\t<tr class='$class'>\n\t\t<td>$n</td>\n\t\t<td>".
html::a_href(htmlspecialchars($name),$base.strtr($file['path'], array(
Api\Html::a_href(htmlspecialchars($name),$base.strtr($file['path'], array(
'%' => '%25',
'#' => '%23',
'?' => '%3F',

View File

@ -33,9 +33,9 @@
+----------------------------------------------------------------------+
*/
require_once "HTTP/WebDAV/Tools/_parse_propfind.php";
require_once "HTTP/WebDAV/Tools/_parse_proppatch.php";
require_once "HTTP/WebDAV/Tools/_parse_lockinfo.php";
require_once __DIR__."/Tools/_parse_propfind.php";
require_once __DIR__."/Tools/_parse_proppatch.php";
require_once __DIR__."/Tools/_parse_lockinfo.php";
/**
* Virtual base class for implementing WebDAV servers

View File

@ -33,8 +33,7 @@
+----------------------------------------------------------------------+
*/
require_once "HTTP/WebDAV/Server.php";
require_once "System.php";
require_once dirname(__DIR__).'/Server.php';
/**
* Filesystem access using WebDAV

View File

@ -5,11 +5,14 @@
* @link http://www.egroupware.org/
* @package filemanager
* @author Ralf Becker <rb-AT-stylite.de>
* @copyright (c) 2014 by Ralf Becker <rb-AT-stylite.de>
* @copyright (c) 2014-16 by Ralf Becker <rb-AT-stylite.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
use EGroupware\Api;
use EGroupware\Api\Vfs\Sharing;
/**
* Filemanager: shares
*/
@ -40,7 +43,7 @@ class filemanager_shares extends filemanager_ui
{
// sudo handling
parent::__construct();
self::$is_setup = egw_session::appsession('is_setup','filemanager');
self::$is_setup = Api\Cache::getSession('filemanager', 'is_setup');
self::$tmp_dir = '/home/'.$GLOBALS['egw_info']['user']['account_lid'].'/.tmp/';
}
@ -57,16 +60,16 @@ class filemanager_shares extends filemanager_ui
{
switch ($query['col_filter']['type'])
{
case egw_sharing::LINK:
case Sharing::LINK:
$query['col_filter'][] = "share_path LIKE ".$GLOBALS['egw']->db->quote(self::$tmp_dir.'%');
break;
case egw_sharing::READONLY:
case Sharing::READONLY:
$query['col_filter'][] = "share_path NOT LIKE ".$GLOBALS['egw']->db->quote(self::$tmp_dir.'%');
$query['col_filter']['share_writable'] = false;
break;
case egw_sharing::WRITABLE:
case Sharing::WRITABLE:
$query['col_filter']['share_writable'] = true;
break;
}
@ -82,18 +85,18 @@ class filemanager_shares extends filemanager_ui
$query['col_filter']['share_owner'] = $GLOBALS['egw_info']['user']['account_id'];
$readonlys = null;
$total = egw_sharing::so()->get_rows($query, $rows, $readonlys);
$total = Sharing::so()->get_rows($query, $rows, $readonlys);
foreach($rows as &$row)
{
if (substr($row['share_path'], 0, strlen(self::$tmp_dir)) === self::$tmp_dir)
{
$row['share_path'] = substr($row['share_path'], strlen(self::$tmp_dir));
$row['type'] = egw_sharing::LINK;
$row['type'] = Sharing::LINK;
}
else
{
$row['type'] = $row['share_writable'] ? egw_sharing::WRITABLE : egw_sharing::READONLY;
$row['type'] = $row['share_writable'] ? Sharing::WRITABLE : Sharing::READONLY;
}
$row['share_passwd'] = (boolean)$row['share_passwd'];
if ($row['share_with']) $row['share_with'] = preg_replace('/,([^ ])/', ', $1', $row['share_with']);
@ -159,25 +162,25 @@ class filemanager_shares extends filemanager_ui
{
$where['share_id'] = $content['nm']['selected'];
}
egw_framework::message(lang('%1 shares deleted.', egw_sharing::delete($where)), 'success');
egw_framework::message(lang('%1 shares deleted.', Sharing::delete($where)), 'success');
break;
default:
throw new egw_exception_wrong_parameter("Unknown action '{$content['nm']['action']}'!");
throw new Api\Exception\WrongParameter("Unknown action '{$content['nm']['action']}'!");
}
unset($content['nm']['action']);
}
$content['is_setup'] = self::$is_setup;
$sel_options = array(
'type' => egw_sharing::$modes,
'type' => Sharing::$modes,
'share_passwd' => array(
'no' => lang('No'),
'yes' => lang('Yes'),
)
);
unset($sel_options['type'][egw_sharing::ATTACH]);
unset($sel_options['type'][Sharing::ATTACH]);
$tpl = new etemplate_new('filemanager.shares');
$tpl = new Api\Etemplate('filemanager.shares');
$tpl->exec('filemanager.filemanager_shares.index', $content, $sel_options, null, $content);
}
}

View File

@ -13,10 +13,13 @@
* @package api
* @subpackage vfs
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-12 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
use EGroupware\Api;
use EGroupware\Vfs;
$starttime = microtime(true);
/**
@ -33,7 +36,7 @@ function check_access(&$account)
{
list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) = explode(':',base64_decode($_GET['auth']),2);
}
return egw_digest_auth::autocreate_session_callback($account);
return Api\Header\Authenticate::autocreate_session_callback($account);
}
$GLOBALS['egw_info'] = array(
@ -43,17 +46,16 @@ $GLOBALS['egw_info'] = array(
'currentapp' => preg_match('|/webdav.php/apps/([A-Za-z0-9_-]+)/|', $_SERVER['REQUEST_URI'], $matches) ? $matches[1] : 'filemanager',
'autocreate_session_callback' => 'check_access',
'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
'auth_realm' => 'EGroupware WebDAV server', // cant use vfs_webdav_server::REALM as autoloading and include path not yet setup!
'auth_realm' => 'EGroupware WebDAV server', // cant use Vfs\WebDAV::REALM as autoloading and include path not yet setup!
)
);
require_once('../phpgwapi/inc/class.egw_digest_auth.inc.php');
// if you move this file somewhere else, you need to adapt the path to the header!
try
{
include(dirname(__DIR__).'/header.inc.php');
}
catch (egw_exception_no_permission_app $e)
catch (Api\Exception\NoPermission\App $e)
{
if (isset($GLOBALS['egw_info']['user']['apps']['filemanager']))
{
@ -73,18 +75,18 @@ catch (egw_exception_no_permission_app $e)
// temporary mount ownCloud default /clientsync as /home/$user, if not explicitly mounted
// so ownCloud dir contains users home-dir by default
if (strpos($_SERVER['REQUEST_URI'],'/webdav.php/clientsync') !== false &&
($fstab=egw_vfs::mount()) && !isset($fstab['/clientsync']))
($fstab=Vfs::mount()) && !isset($fstab['/clientsync']))
{
$is_root_backup = egw_vfs::$is_root;
egw_vfs::$is_root = true;
$ok = egw_vfs::mount($url='vfs://default/home/$user', $clientsync='/clientsync', null, false);
egw_vfs::$is_root = $is_root_backup;
$is_root_backup = Vfs::$is_root;
Vfs::$is_root = true;
$ok = Vfs::mount($url='vfs://default/home/$user', $clientsync='/clientsync', null, false);
Vfs::$is_root = $is_root_backup;
//error_log("mounting ownCloud default '$clientsync' as '$url' ".($ok ? 'successful' : 'failed!'));
}
// webdav is stateless: we dont need to keep the session open, it only blocks other calls to same basic-auth session
$GLOBALS['egw']->session->commit_session();
$webdav_server = new vfs_webdav_server();
$webdav_server = new Vfs\WebDAV();
$webdav_server->ServeRequest();
//error_log(sprintf('WebDAV %s request: status "%s", took %5.3f s'.($headertime?' (header include took %5.3f s)':''),$_SERVER['REQUEST_METHOD'].' '.$_SERVER['PATH_INFO'],$webdav_server->_http_status,microtime(true)-$starttime,$headertime-$starttime));

View File

@ -16,7 +16,7 @@
* @package api
* @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
@ -36,13 +36,12 @@ $GLOBALS['egw_info'] = array(
'noheader' => True,
'currentapp' => 'groupdav',
'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
'autocreate_session_callback' => array('egw_digest_auth','autocreate_session_callback'),
'autocreate_session_callback' => 'EGroupware\\Api\\Header\\Authenticate::autocreate_session_callback',
'auth_realm' => 'EGroupware CalDAV/CardDAV/GroupDAV server', // cant use groupdav::REALM as autoloading and include path not yet setup!
)
);
// if you move this file somewhere else, you need to adapt the path to the header!
$egw_dir = dirname(__FILE__);
require_once($egw_dir.'/phpgwapi/inc/class.egw_digest_auth.inc.php');
include($egw_dir.'/header.inc.php');
$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository();

View File

@ -1,6 +1,6 @@
<?php
/**
* eGroupWare API: Basic and Digest Auth
* EGroupware API: Basic and Digest Auth
*
* For Apache FCGI you need the following rewrite rule:
*
@ -15,256 +15,15 @@
* @package api
* @subpackage auth
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2010 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2010-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
use EGroupware\Api\Header\Authenticate;
/**
* Class to authenticate via basic or digest auth
*
* The more secure digest auth requires:
* a) cleartext passwords in SQL table
* b) md5 hashes of username, realm, password stored somewhere (NOT yet implemented)
* Otherwise digest auth is not possible and therefore not offered to the client.
*
* Usage example:
*
* $GLOBALS['egw_info']['flags'] = array(
* 'noheader' => True,
* 'currentapp' => 'someapp',
* 'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
* 'autocreate_session_callback' => array('egw_digest_auth','autocreate_session_callback'),
* 'auth_realm' => 'EGroupware',
* );
* include(dirname(__FILE__).'/header.inc.php');
*
* @link http://www.php.net/manual/en/features.http-auth.php
* @ToDo check if we have to check if returned nonce matches our challange (not done in above link, but why would it be there)
* @link http://en.wikipedia.org/wiki/Digest_access_authentication
* @link http://tools.ietf.org/html/rfc2617
*
* Commented out is accept-charset parameter from (seems not supported by any client I tested with)
* @link https://tools.ietf.org/id/draft-reschke-basicauth-enc-06.html
*
* Implemented support for clients sending credentials in in iso-8859-1 instead of our utf-8:
* - Firefox 19.0
* - Thunderbird 17.0.3 with Lightning 1.8
* - IE 8
* - Netdrive
* (Chrome 24 or Safari 6 sends credentials in charset of webpage.)
* @deprecated use EGroupware\Api\Header\Authenticate
*/
class egw_digest_auth
{
/**
* Log to error_log:
* 0 = dont
* 1 = no cleartext passwords
* 2 = all
*/
const ERROR_LOG = 0;
/**
* Callback to be used to create session via header include authenticated via basic or digest auth
*
* @param array $account NOT used!
* @return string valid session-id or does NOT return at all!
*/
static public function autocreate_session_callback(&$account)
{
unset($account); // not used, but required by function signature
if (self::ERROR_LOG)
{
$pw = self::ERROR_LOG > 1 ? $_SERVER['PHP_AUTH_PW'] : '**********';
error_log(__METHOD__.'() PHP_AUTH_USER='.array2string($_SERVER['PHP_AUTH_USER']).', PHP_AUTH_PW='.array2string($pw).', PHP_AUTH_DIGEST='.array2string($_SERVER['PHP_AUTH_DIGEST']));
}
$realm = $GLOBALS['egw_info']['flags']['auth_realm'];
if (empty($realm)) $realm = 'EGroupware';
$username = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW'];
// Support for basic auth when using PHP CGI (what about digest auth?)
if (!isset($username) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'],'Basic ') === 0)
{
$hash = base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'],6));
if (strpos($hash, ':') !== false)
{
list($username, $password) = explode(':', $hash, 2);
}
}
elseif (isset($_SERVER['PHP_AUTH_DIGEST']) && !self::is_valid($realm,$_SERVER['PHP_AUTH_DIGEST'],$username,$password))
{
unset($password);
}
// if given password contains non-ascii chars AND we can not authenticate with it
if (isset($username) && isset($password) &&
(preg_match('/[^\x20-\x7F]/', $password) || strpos($password, '\\x') !== false) &&
!$GLOBALS['egw']->auth->authenticate($username, $password, 'text'))
{
self::decode_password($password);
}
// create session without session cookie (session->create(..., true), as we use pseudo sessionid from credentials
if (!isset($username) || !($sessionid = $GLOBALS['egw']->session->create($username, $password, 'text', true)))
{
// if the session class gives a reason why the login failed --> append it to the REALM
if ($GLOBALS['egw']->session->reason) $realm .= ': '.$GLOBALS['egw']->session->reason;
header('WWW-Authenticate: Basic realm="'.$realm.'"');// draft-reschke-basicauth-enc-06 adds, accept-charset="'.translation::charset().'"');
self::digest_header($realm);
header('HTTP/1.1 401 Unauthorized');
header('X-WebDAV-Status: 401 Unauthorized', true);
echo "<html>\n<head>\n<title>401 Unauthorized</title>\n<body>\nAuthorization failed.\n</body>\n</html>\n";
exit;
}
return $sessionid;
}
/**
* Decode password containing non-ascii chars
*
* @param string &$password
* @return boolean true if conversation happend, false if there was no need for a conversation
*/
public static function decode_password(&$password)
{
// if given password contains non-ascii chars AND we can not authenticate with it
if (preg_match('/[^\x20-\x7F]/', $password) || strpos($password, '\\x') !== false)
{
// replace \x encoded non-ascii chars in password, as they are used eg. by Thunderbird for German umlauts
if (strpos($password, '\\x') !== false)
{
$password = preg_replace_callback('/\\\\x([0-9A-F]{2})/i', function($matches){
return chr(hexdec($matches[1]));
}, $password);
}
// try translating the password from iso-8859-1 to utf-8
$password = translation::convert($password, 'iso-8859-1');
//error_log(__METHOD__."() Fixed non-ascii password of user '$username' from '$_SERVER[PHP_AUTH_PW]' to '$password'");
return true;
}
return false;
}
/**
* Check if digest auth is available for a given realm (and user): do we use cleartext passwords
*
* If no user is given, check is NOT authoretive, as we can only check if cleartext passwords are generally used
*
* @param string $realm
* @param string $username =null username or null to only check if we auth agains sql and use plaintext passwords
* @param string &$user_pw =null stored cleartext password, if $username given AND function returns true
* @return boolean true if digest auth is available, false otherwise
*/
static public function digest_auth_available($realm,$username=null,&$user_pw=null)
{
// we currently require plaintext passwords!
if (!($GLOBALS['egw_info']['server']['auth_type'] == 'sql' && $GLOBALS['egw_info']['server']['sql_encryption_type'] == 'plain') ||
$GLOBALS['egw_info']['server']['auth_type'] == 'ldap' && $GLOBALS['egw_info']['server']['ldap_encryption_type'] == 'plain')
{
if (self::ERROR_LOG) error_log(__METHOD__."('$username') return false (no plaintext passwords used)");
return false; // no plain-text passwords used
}
// check for specific user, if given
if (!is_null($username) && !(($user_pw = $GLOBALS['egw']->accounts->id2name($username,'account_pwd','u')) ||
$GLOBALS['egw_info']['server']['auth_type'] == 'sql' && substr($user_pw,0,7) != '{PLAIN}'))
{
unset($user_pw);
if (self::ERROR_LOG) error_log(__METHOD__."('$realm','$username') return false (unknown user or NO plaintext password for user)");
return false; // user does NOT exist, or has no plaintext passwords (ldap server requires real root_dn or special ACL!)
}
if (substr($user_pw,0,7) == '{PLAIN}') $user_pw = substr($user_pw,7);
if (self::ERROR_LOG)
{
$pw = self::ERROR_LOG > 1 ? $user_pw : '**********';
error_log(__METHOD__."('$realm','$username','$pw') return true");
}
return true;
}
/**
* Send header offering digest auth, if it's generally available
*
* @param string $realm
* @param string &$nonce=null on return
*/
static public function digest_header($realm,&$nonce=null)
{
if (self::digest_auth_available($realm))
{
$nonce = uniqid();
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.$nonce.'",opaque="'.md5($realm).'"');
if (self::ERROR_LOG) error_log(__METHOD__."() offering digest auth for realm '$realm' using nonce='$nonce'");
}
}
/**
* Check digest
*
* @param string $realm
* @param string $auth_digest =null default to $_SERVER['PHP_AUTH_DIGEST']
* @param string &$username on return username
* @param string &$password on return cleartext password
* @return boolean true if digest is correct, false otherwise
*/
static public function is_valid($realm,$auth_digest=null,&$username=null,&$password=null)
{
if (is_null($auth_digest)) $auth_digest = $_SERVER['PHP_AUTH_DIGEST'];
$data = self::parse_digest($auth_digest);
if (!$data || !($A1 = self::get_digest_A1($realm,$username=$data['username'],$password=null)))
{
error_log(__METHOD__."('$realm','$auth_digest','$username') returning FALSE");
return false;
}
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if (self::ERROR_LOG) error_log(__METHOD__."('$realm','$auth_digest','$username') response='$data[response]', valid_response='$valid_response' returning ".array2string($data['response'] === $valid_response));
return $data['response'] === $valid_response;
}
/**
* Calculate the A1 digest hash
*
* @param string $realm
* @param string $username
* @param string &$password=null password to use or if null, on return stored password
* @return string|boolean false if $password not given and can NOT be read
*/
static private function get_digest_A1($realm,$username,&$password=null)
{
$user_pw = null;
if (empty($username) || empty($realm) || !self::digest_auth_available($realm,$username,$user_pw))
{
return false;
}
if (is_null($password)) $password = $user_pw;
$A1 = md5($username . ':' . $realm . ':' . $password);
if (self::ERROR_LOG > 1) error_log(__METHOD__."('$realm','$username','$password') returning ".array2string($A1));
return $A1;
}
/**
* Parse the http auth header
*/
static public function parse_digest($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));
$matches = null;
preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m)
{
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}
//error_log(__METHOD__."('$txt') returning ".array2string($needed_parts ? false : $data));
return $needed_parts ? false : $data;
}
}
class egw_digest_auth extends Authenticate {}

View File

@ -7,15 +7,11 @@
* @package api
* @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-14 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
if (strpos(ini_get('include_path'), EGW_API_INC) === false)
{
ini_set('include_path', EGW_API_INC.PATH_SEPARATOR.ini_get('include_path'));
}
require_once('HTTP/WebDAV/Server.php');
require_once(EGW_INCLUDE_ROOT.'/api/src/WebDAV/Server.php');
/**
* EGroupware: GroupDAV access

View File

@ -13,10 +13,13 @@
* @package api
* @subpackage vfs
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-12 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
use EGroupware\Api;
use EGroupware\Vfs;
$starttime = microtime(true);
/**
@ -33,7 +36,7 @@ function check_access(&$account)
{
list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) = explode(':',base64_decode($_GET['auth']),2);
}
return egw_digest_auth::autocreate_session_callback($account);
return Api\Header\Authenticate::autocreate_session_callback($account);
}
$GLOBALS['egw_info'] = array(
@ -43,17 +46,16 @@ $GLOBALS['egw_info'] = array(
'currentapp' => preg_match('|/remote.php/webdav/apps/([A-Za-z0-9_-]+)/|', $_SERVER['REQUEST_URI'], $matches) ? $matches[1] : 'filemanager',
'autocreate_session_callback' => 'check_access',
'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
'auth_realm' => 'EGroupware WebDAV server', // cant use vfs_webdav_server::REALM as autoloading and include path not yet setup!
'auth_realm' => 'EGroupware WebDAV server', // cant use Vfs\WebDAV::REALM as autoloading and include path not yet setup!
)
);
require_once(__DIR__.'/phpgwapi/inc/class.egw_digest_auth.inc.php');
// if you move this file somewhere else, you need to adapt the path to the header!
try
{
include(__DIR__.'/header.inc.php');
}
catch (egw_exception_no_permission_app $e)
catch (Api\Exception\NoPermission\App $e)
{
if (isset($GLOBALS['egw_info']['user']['apps']['filemanager']))
{
@ -73,18 +75,18 @@ catch (egw_exception_no_permission_app $e)
// temporary mount ownCloud default /clientsync as /home/$user, if not explicitly mounted
// so ownCloud dir contains users home-dir by default
if (strpos($_SERVER['REQUEST_URI'],'/remote.php/webdav/clientsync') !== false &&
($fstab=egw_vfs::mount()) && !isset($fstab['/clientsync']))
($fstab=Vfs::mount()) && !isset($fstab['/clientsync']))
{
$is_root_backup = egw_vfs::$is_root;
egw_vfs::$is_root = true;
$ok = egw_vfs::mount($url='vfs://default/home/$user', $clientsync='/clientsync', null, false);
egw_vfs::$is_root = $is_root_backup;
$is_root_backup = Vfs::$is_root;
Vfs::$is_root = true;
$ok = Vfs::mount($url='vfs://default/home/$user', $clientsync='/clientsync', null, false);
Vfs::$is_root = $is_root_backup;
//error_log("mounting ownCloud default '$clientsync' as '$url' ".($ok ? 'successful' : 'failed!'));
}
// webdav is stateless: we dont need to keep the session open, it only blocks other calls to same basic-auth session
$GLOBALS['egw']->session->commit_session();
$webdav_server = new vfs_webdav_server();
$webdav_server = new Vfs\WebDAV();
$webdav_server->ServeRequest('/webdav');
//error_log(sprintf('WebDAV %s request: status "%s", took %5.3f s'.($headertime?' (header include took %5.3f s)':''),$_SERVER['REQUEST_METHOD'].' '.$_SERVER['PATH_INFO'],$webdav_server->_http_status,microtime(true)-$starttime,$headertime-$starttime));

View File

@ -6,11 +6,13 @@
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @author Ralf Becker <rb@stylite.de>
* @copyright (c) 2014/15 by Ralf Becker <rb@stylite.de>
* @copyright (c) 2014-16 by Ralf Becker <rb@stylite.de>
* @version $Id$
*/
require_once('./phpgwapi/inc/class.egw_sharing.inc.php');
require_once(__DIR__.'/api/src/Vfs/Sharing.php');
use EGroupware\Api\Vfs\Sharing;
$GLOBALS['egw_info'] = array(
'flags' => array(
@ -18,7 +20,7 @@ $GLOBALS['egw_info'] = array(
'noheader' => true,
'nonavbar' => 'always', // true would cause eTemplate to reset it to false for non-popups!
'currentapp' => 'filemanager',
'autocreate_session_callback' => 'egw_sharing::create_session',
'autocreate_session_callback' => 'EGroupware\\Api\\Vfs\\Sharing::create_session',
'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
)
);
@ -27,6 +29,6 @@ include('./header.inc.php');
if (!$GLOBALS['egw']->sharing)
{
egw_sharing::create_session(true); // true = mount into existing session
Sharing::create_session(true); // true = mount into existing session
}
$GLOBALS['egw']->sharing->ServeRequest();

View File

@ -16,10 +16,13 @@
* @package api
* @subpackage vfs
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
use EGroupware\Api;
use EGroupware\Api\Vfs;
$starttime = microtime(true);
/**
@ -36,7 +39,7 @@ function check_access(&$account)
{
list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) = explode(':',base64_decode($_GET['auth']),2);
}
return egw_digest_auth::autocreate_session_callback($account);
return Api\Header\Authenticate::autocreate_session_callback($account);
}
$GLOBALS['egw_info'] = array(
@ -46,17 +49,16 @@ $GLOBALS['egw_info'] = array(
'currentapp' => preg_match('|/webdav.php/apps/([A-Za-z0-9_-]+)/|', $_SERVER['REQUEST_URI'], $matches) ? $matches[1] : 'filemanager',
'autocreate_session_callback' => 'check_access',
'no_exception_handler' => 'basic_auth', // we use a basic auth exception handler (sends exception message as basic auth realm)
'auth_realm' => 'EGroupware WebDAV server', // cant use vfs_webdav_server::REALM as autoloading and include path not yet setup!
'auth_realm' => 'EGroupware WebDAV server', // cant use Vfs\WebDAV::REALM as autoloading and include path not yet setup!
)
);
require_once('phpgwapi/inc/class.egw_digest_auth.inc.php');
// if you move this file somewhere else, you need to adapt the path to the header!
try
{
include(dirname(__FILE__).'/header.inc.php');
}
catch (egw_exception_no_permission_app $e)
catch (Api\Exception\NoPermission\App $e)
{
if (isset($GLOBALS['egw_info']['user']['apps']['filemanager']))
{
@ -76,6 +78,6 @@ catch (egw_exception_no_permission_app $e)
// webdav is stateless: we dont need to keep the session open, it only blocks other calls to same basic-auth session
$GLOBALS['egw']->session->commit_session();
$webdav_server = new vfs_webdav_server();
$webdav_server = new Vfs\WebDAV();
$webdav_server->ServeRequest();
//error_log(sprintf('WebDAV %s request: status "%s", took %5.3f s'.($headertime?' (header include took %5.3f s)':''),$_SERVER['REQUEST_METHOD'].' '.$_SERVER['PATH_INFO'],$webdav_server->_http_status,microtime(true)-$starttime,$headertime-$starttime));