mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-02-22 21:30:54 +01:00
added support for PHP running as (F)CGI (uses ORIG_PATH_INFO instead of PATH_INFO), also added basic digest auth support from trunk
This commit is contained in:
parent
7fc153654e
commit
4cee5102af
@ -169,6 +169,11 @@ class HTTP_WebDAV_Server
|
||||
// default uri is the complete request uri
|
||||
$uri = (@$this->_SERVER["HTTPS"] === "on" ? "https:" : "http:") . '//'.$this->_SERVER['HTTP_HOST'];
|
||||
}
|
||||
// support for PHP running as (F)CGI
|
||||
if (!isset($this->_SERVER['PATH_INFO']) && isset($this->_SERVER['ORIG_PATH_INFO']))
|
||||
{
|
||||
$this->_SERVER['PATH_INFO'] = $this->_SERVER['ORIG_PATH_INFO'];
|
||||
}
|
||||
// we cant use SCRIPT_NAME, because it fails, if there's any url rewriting
|
||||
//error_log("pathinfo:\n". $this->_urldecode($this->_SERVER['REQUEST_URI']).":\n".$this->_SERVER['PATH_INFO']);
|
||||
$uri .= $this->_urldecode($this->_SERVER['REQUEST_URI']);
|
||||
|
@ -293,8 +293,8 @@ class egw extends egw_minimal
|
||||
// check if we have a session, if not try to automatic create one
|
||||
if ($this->session->verify()) return true;
|
||||
|
||||
if (($account_callback = $GLOBALS['egw_info']['flags']['autocreate_session_callback']) && function_exists($account_callback) &&
|
||||
($sessionid = $account_callback($account)) === true) // $account_call_back returns true, false or a session-id
|
||||
if (($account_callback = $GLOBALS['egw_info']['flags']['autocreate_session_callback']) && is_callable($account_callback) &&
|
||||
($sessionid = call_user_func_array($account_callback,array(&$account))) === true) // $account_call_back returns true, false or a session-id
|
||||
{
|
||||
$sessionid = $this->session->create($account);
|
||||
}
|
||||
@ -487,6 +487,10 @@ class egw extends egw_minimal
|
||||
print("\n\n");
|
||||
}
|
||||
@ob_flush(); flush();
|
||||
|
||||
// commit session (if existing), to fix timing problems sometimes preventing session creation ("Your session can not be verified")
|
||||
if (isset($GLOBALS['egw']->session)) $GLOBALS['egw']->session->commit_session();
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
213
phpgwapi/inc/class.egw_digest_auth.inc.php
Normal file
213
phpgwapi/inc/class.egw_digest_auth.inc.php
Normal file
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* eGroupWare API: Basic and Digest Auth
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package api
|
||||
* @subpackage auth
|
||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @copyright (c) 2010 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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)
|
||||
{
|
||||
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';
|
||||
|
||||
// Support for basic auth when using PHP CGI (what about digest auth?)
|
||||
if (!isset($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['Authorization']) && strpos($_SERVER['Authorization'],'Basic ') === 0)
|
||||
{
|
||||
$hash = base64_decode(substr($_SERVER['Authorization'],6));
|
||||
if (strpos($hash, ':') !== false)
|
||||
{
|
||||
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', $hash, 2);
|
||||
}
|
||||
}
|
||||
if (!isset($_SERVER['PHP_AUTH_USER']) && !isset($_SERVER['PHP_AUTH_DIGEST']) ||
|
||||
isset($_SERVER['PHP_AUTH_DIGEST']) && (!self::is_valid($realm,$_SERVER['PHP_AUTH_DIGEST'],$username,$password) ||
|
||||
!($sessionid = $GLOBALS['egw']->session->create($username,$password,'text'))) ||
|
||||
isset($_SERVER['PHP_AUTH_USER']) && !($sessionid = $GLOBALS['egw']->session->create($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'],'text')))
|
||||
{
|
||||
// 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.'"');
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -206,7 +206,7 @@ class egw_session
|
||||
$GLOBALS['egw_info']['server']['max_history'] = 20;
|
||||
$save_rep = true;
|
||||
}
|
||||
|
||||
|
||||
if ($save_rep)
|
||||
{
|
||||
$config = new config('phpgwapi');
|
||||
@ -741,6 +741,15 @@ class egw_session
|
||||
// we generate a pseudo-sessionid from the basic auth credentials
|
||||
$sessionid = md5($_SERVER['PHP_AUTH_USER'].':'.$_SERVER['PHP_AUTH_PW'].':'.$_SERVER['HTTP_HOST'].':'.EGW_SERVER_ROOT.':'.self::getuser_ip());
|
||||
}
|
||||
// same for digest auth
|
||||
elseif (isset($_SERVER['PHP_AUTH_DIGEST']) &&
|
||||
in_array(basename($_SERVER['SCRIPT_NAME']),array('webdav.php','groupdav.php')))
|
||||
{
|
||||
// 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']);
|
||||
$sessionid = md5($data['username'].':'.$data['realm'].':'.$data['nonce'].':'.$_SERVER['HTTP_HOST'].':'.EGW_SERVER_ROOT.':'.self::getuser_ip());
|
||||
}
|
||||
elseif(!$only_basic_auth && isset($_REQUEST[self::EGW_SESSION_NAME]))
|
||||
{
|
||||
$sessionid = $_REQUEST[self::EGW_SESSION_NAME];
|
||||
@ -1256,7 +1265,7 @@ class egw_session
|
||||
public static function search_instance($login,$domain_requested,&$default_domain,$server_name,array $domains=null)
|
||||
{
|
||||
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."('$login','$domain_requested',".array2string($default_domain).".'$server_name'.".array2string($domains).")");
|
||||
|
||||
|
||||
if (is_null($domains)) $domains = $GLOBALS['egw_domain'];
|
||||
|
||||
if (!isset($default_domain) || !isset($domains[$default_domain])) // allow to overwrite the default domain
|
||||
@ -1301,7 +1310,7 @@ class egw_session
|
||||
$domain = $default_domain;
|
||||
}
|
||||
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."() default_domain=".array2string($default_domain).', login='.array2string($login)." returning ".array2string($domain));
|
||||
|
||||
|
||||
return $domain;
|
||||
}
|
||||
|
||||
|
97
webdav.php
Normal file
97
webdav.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* FileManger - WebDAV access
|
||||
*
|
||||
* Using the PEAR HTTP/WebDAV/Server class (which need to be installed!)
|
||||
*
|
||||
* @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 <RalfBecker-AT-outdoor-training.de>
|
||||
* @copyright (c) 2006-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
$starttime = microtime(true);
|
||||
|
||||
/**
|
||||
* check if the given user has access
|
||||
*
|
||||
* Create a session or if the user has no account return authenticate header and 401 Unauthorized
|
||||
*
|
||||
* @param array &$account
|
||||
* @return int session-id
|
||||
*/
|
||||
function check_access(&$account)
|
||||
{
|
||||
if (isset($_GET['auth']))
|
||||
{
|
||||
list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']) = explode(':',base64_decode($_GET['auth']),2);
|
||||
}
|
||||
return egw_digest_auth::autocreate_session_callback($account);
|
||||
}
|
||||
|
||||
// if we are called with a /apps/$app path, use that $app as currentapp, to not require filemanager rights for the links
|
||||
$parts = explode('/',isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : $_SERVER['ORIG_PATH_INFO']);
|
||||
//error_log("webdav: explode".print_r($parts,true));
|
||||
if(count($parts) == 1)
|
||||
{
|
||||
error_log(__METHOD__. "Malformed Url: missing slash:\n".$_SERVER['SERVER_NAME']."\n PATH_INFO:".$_SERVER['PATH_INFO'].
|
||||
"\n REQUEST_URI".$_SERVER['REQUEST_URI']."\n ORIG_SCRIPT_NAME:".$_SERVER['ORIG_SCRIPT_NAME'].
|
||||
"\n REMOTE_ADDR:".$_SERVER['REMOTE_ADDR']."\n PATH_INFO:".$_SERVER['PATH_INFO']."\n HTTP_USER_AGENT:".$_SERVER['HTTP_USER_AGENT']) ;
|
||||
header("HTTP/1.1 501 Not implemented");
|
||||
header("X-WebDAV-Status: 501 Not implemented", true);
|
||||
exit;
|
||||
}
|
||||
|
||||
$app = count($parts) > 3 && $parts[1] == 'apps' ? $parts[2] : 'filemanager';
|
||||
|
||||
$GLOBALS['egw_info'] = array(
|
||||
'flags' => array(
|
||||
'disable_Template_class' => True,
|
||||
'noheader' => True,
|
||||
'currentapp' => $app,
|
||||
'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!
|
||||
)
|
||||
);
|
||||
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)
|
||||
{
|
||||
if (isset($GLOBALS['egw_info']['user']['apps']['filemanager']))
|
||||
{
|
||||
$GLOBALS['egw_info']['currentapp'] = 'filemanager';
|
||||
}
|
||||
elseif (isset($GLOBALS['egw_info']['user']['apps']['sitemgr-link']))
|
||||
{
|
||||
$GLOBALS['egw_info']['currentapp'] = 'sitemgr-link';
|
||||
}
|
||||
else
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
//$headertime = microtime(true);
|
||||
|
||||
// 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();
|
||||
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
|
||||
if (strstr($user_agent, 'microsoft-webdav') !== false ||
|
||||
strstr($user_agent, 'neon') !== false ||
|
||||
strstr($user_agent, 'bitkinex') !== false)
|
||||
{
|
||||
// Windows 7 et.al. special treatment
|
||||
$webdav_server->cnrnd = true;
|
||||
}
|
||||
$webdav_server->ServeRequest();
|
||||
//error_log(sprintf("WebDAV %s request took %5.3f s (header include took %5.3f s)",$_SERVER['REQUEST_METHOD'],microtime(true)-$starttime,$headertime-$starttime));
|
Loading…
Reference in New Issue
Block a user