New class to handle requests, so we can extract permissions in a better

way.  Of course this breaks all of the existing code, so don't use this
until I have all the regression tests passing again :-)
This commit is contained in:
Andrew McMillan 2006-11-27 00:16:01 +13:00
parent 03ba7469cc
commit b591115b72
4 changed files with 237 additions and 115 deletions

View File

@ -207,7 +207,7 @@ DECLARE
BEGIN
-- Self can always have full access
IF in_from = in_to THEN
RETURN ''RW'';
RETURN ''A'';
END IF;
-- dbg := ''S-'';

View File

@ -12,115 +12,10 @@ require_once("always.php");
dbg_error_log( "caldav", " User agent: %s", ((isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "Unfortunately Mulberry and Chandler don't send a 'User-agent' header with their requests :-(")) );
require_once("BasicAuthSession.php");
$raw_headers = apache_request_headers();
$raw_post = file_get_contents ( 'php://input');
require_once("CalDAVRequest.php");
$request = new CalDAVRequest();
if ( isset($debugging) && isset($_GET['method']) ) {
$_SERVER['REQUEST_METHOD'] = $_GET['method'];
}
/**
* A variety of requests may set the "Depth" header to control recursion
*/
$query_depth = ( isset($_SERVER['HTTP_DEPTH']) ? $_SERVER['HTTP_DEPTH'] : 0 );
if ( $query_depth == 'infinite' ) $query_depth = 99;
$query_depth = intval($query_depth);
/**
* Our path is /<script name>/<user name>/<user controlled> if it ends in
* a trailing '/' then it is referring to a DAV 'collection' but otherwise
* it is referring to a DAV data item.
*
* Permissions are controlled as follows:
* 1. if there is no <user name> component, the request has read privileges
* 2. if the requester is an admin, the request has read/write priviliges
* 3. if there is a <user name> component which matches the logged on user
* then the request has read/write privileges
* 4. otherwise we query the defined relationships between users and use
* the minimum privileges returned from that analysis.
*/
$request_path = $_SERVER['PATH_INFO'];
$bad_chars_regex = '/[\\^\\[\\(\\\\]/';
if ( preg_match( $bad_chars_regex, $request_path ) ) {
header("HTTP/1.1 400 Bad Request");
header("Content-type: text/plain");
echo "The calendar path contains illegal characters.";
dbg_error_log("caldav", "Illegal characters /%s/ in calendar path for User: %d, Path: %s", $bad_chars_regex, $session->user_no, $request_path);
exit(0);
}
dbg_error_log("caldav", "Legal characters /%s/ in calendar path for User: %d, Path: %s", $bad_chars_regex, $session->user_no, $request_path);
$path_split = preg_split('#/+#', $request_path );
$permissions = array();
if ( !isset($path_split[1]) || $path_split[1] == '' ) {
dbg_error_log( "caldav", "No useful path split possible" );
unset($path_user_no);
unset($path_username);
$permissions = array("read" => 'read' );
dbg_error_log( "caldav", "Read permissions for user accessing /" );
}
else {
$path_username = $path_split[1];
@dbg_error_log( "caldav", "Path split into at least /// %s /// %s /// %s", $path_split[1], $path_split[2], $path_split[3] );
$qry = new PgQuery( "SELECT * FROM usr WHERE username = ?;", $path_username );
if ( $qry->Exec("caldav") && $path_user_record = $qry->Fetch() ) {
$path_user_no = $path_user_record->user_no;
}
if ( $session->AllowedTo("Admin") ) {
$permissions = array('all' => 'all' );
dbg_error_log( "caldav", "Full permissions for a systems administrator" );
}
else if ( $session->user_no == $path_user_no ) {
$permissions = array('all' => 'all' );
dbg_error_log( "caldav", "Full permissions for user accessing their own hierarchy" );
}
else if ( isset($path_user_no) ) {
/**
* We need to query the database for permissions
*/
$qry = new PgQuery( "SELECT get_permissions( ?, ? ) AS perm;", $session->user_no, $path_user_no);
if ( $qry->Exec("caldav") && $permission_result = $qry->Fetch() ) {
$permission_result = "!".$permission_result->perm; // We prepend something to ensure we get a non-zero position.
$permissions = array();
if ( strpos($permission_result,"R") ) $permissions['read'] = 'read';
if ( strpos($permission_result,"W") )
$permissions['write'] = 'write';
else {
if ( strpos($permission_result,"C") ) $permissions['bind'] = 'bind'; // PUT of new content (i.e. Create)
if ( strpos($permission_result,"D") ) $permissions['unbind'] = 'unbind'; // DELETE
if ( strpos($permission_result,"M") ) $permissions['write-content'] = 'write-content'; // DELETE
}
}
dbg_error_log( "caldav", "Restricted permissions for user accessing someone elses hierarchy: read=%s, write=%s", isset($permissions['read']), isset($permissions['write']) );
}
}
/**
* If the content we are receiving is XML then we parse it here.
*/
$xml_parser = xml_parser_create_ns('UTF-8');
$xml_tags = array();
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parse_into_struct( $xml_parser, $raw_post, $xml_tags );
xml_parser_free($xml_parser);
unset($etag_if_match);
unset($etag_none_match);
if ( isset($_SERVER["HTTP_IF_NONE_MATCH"]) ) {
$etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
if ( $etag_none_match == '' ) unset($etag_none_match);
}
if ( isset($_SERVER["HTTP_IF_MATCH"]) ) {
$etag_if_match = str_replace('"','',$_SERVER["HTTP_IF_MATCH"]);
if ( $etag_if_match == '' ) unset($etag_if_match);
}
/**
* We put the code for each type of request into a separate include file
*/
$request_method = $_SERVER['REQUEST_METHOD'];
switch ( $request_method ) {
switch ( $request->method ) {
case 'OPTIONS': include_once("caldav-OPTIONS.php"); break;
case 'REPORT': include_once("caldav-REPORT.php"); break;
case 'PROPFIND': include_once("caldav-PROPFIND.php"); break;
@ -133,12 +28,11 @@ switch ( $request_method ) {
case 'DELETE': include_once("caldav-DELETE.php"); break;
default:
dbg_error_log( "caldav", "Unhandled request method >>%s<<", $_SERVER['REQUEST_METHOD'] );
dbg_log_array( "caldav", 'HEADERS', $raw_headers );
dbg_error_log( "caldav", "Unhandled request method >>%s<<", $request->method );
dbg_log_array( "caldav", '_SERVER', $_SERVER, true );
dbg_error_log( "caldav", "RAW: %s", str_replace("\n", "",str_replace("\r", "", $raw_post)) );
dbg_error_log( "caldav", "RAW: %s", str_replace("\n", "",str_replace("\r", "", $request->raw_post)) );
}
exit(0);
$this->RequestExitStatus( 500, translate("The application program does not understand that request.") );
?>

227
inc/CalDAVRequest.php Normal file
View File

@ -0,0 +1,227 @@
<?php
/**
* Functions that are needed for all CalDAV Requests
*
* - Ascertaining the paths
* - Ascertaining the current user's permission to those paths.
* - Utility functions which we can use to decide whether this
* is a permitted activity for this user.
*
* @package rscds
* @subpackage CalDAVRequest
* @author Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Andrew McMillan
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
/**
* A class for collecting things to do with this request.
*
* @package rscds
*/
class CalDAVRequest
{
/**
* Create a new CalDAVRequest object.
*/
function CalDAVRequest( ) {
global $session, $c, $debugging;
$this->raw_post = file_get_contents ( 'php://input');
if ( isset($debugging) && isset($_GET['method']) ) {
$_SERVER['REQUEST_METHOD'] = $_GET['method'];
}
$this->method = $_SERVER['REQUEST_METHOD'];
/**
* A variety of requests may set the "Depth" header to control recursion
*/
$this->depth = ( isset($_SERVER['HTTP_DEPTH']) ? $_SERVER['HTTP_DEPTH'] : 0 );
if ( $this->depth == 'infinite' ) $this->depth = 99;
$this->depth = intval($this->depth);
/**
* Our path is /<script name>/<user name>/<user controlled> if it ends in
* a trailing '/' then it is referring to a DAV 'collection' but otherwise
* it is referring to a DAV data item.
*
* Permissions are controlled as follows:
* 1. if there is no <user name> component, the request has read privileges
* 2. if the requester is an admin, the request has read/write priviliges
* 3. if there is a <user name> component which matches the logged on user
* then the request has read/write privileges
* 4. otherwise we query the defined relationships between users and use
* the minimum privileges returned from that analysis.
*/
$this->path = $_SERVER['PATH_INFO'];
$bad_chars_regex = '/[\\^\\[\\(\\\\]/';
if ( preg_match( $bad_chars_regex, $this->path ) ) {
$this->RequestExitStatus( 400, translate("The calendar path contains illegal characters.") );
}
$path_split = preg_split('#/+#', $this->path );
$this->permissions = array();
if ( !isset($path_split[1]) || $path_split[1] == '' ) {
dbg_error_log( "caldav", "No useful path split possible" );
unset($this->user_no);
unset($this->username);
$this->permissions = array("read" => 'read' );
dbg_error_log( "caldav", "Read permissions for user accessing /" );
}
else {
$this->username = $path_split[1];
@dbg_error_log( "caldav", "Path split into at least /// %s /// %s /// %s", $path_split[1], $path_split[2], $path_split[3] );
$qry = new PgQuery( "SELECT * FROM usr WHERE username = ?;", $this->username );
if ( $qry->Exec("caldav") && $user = $qry->Fetch() ) {
$this->user_no = $user->user_no;
}
if ( $session->AllowedTo("Admin") ) {
$this->permissions = array('all' => 'all' );
dbg_error_log( "caldav", "Full permissions for a systems administrator" );
}
else if ( $session->user_no == $this->user_no ) {
$this->permissions = array('all' => 'all' );
dbg_error_log( "caldav", "Full permissions for user accessing their own hierarchy" );
}
else if ( isset($this->user_no) ) {
/**
* We need to query the database for permissions
*/
$qry = new PgQuery( "SELECT get_permissions( ?, ? ) AS perm;", $session->user_no, $this->user_no);
if ( $qry->Exec("caldav") && $permission_result = $qry->Fetch() ) {
$permission_result = "!".$permission_result->perm; // We prepend something to ensure we get a non-zero position.
$this->permissions = array();
if ( strpos($permission_result,"A") )
$this->permissions['all'] = 'all';
else {
if ( strpos($permission_result,"R") ) $this->permissions['read'] = 'read';
if ( strpos($permission_result,"W") )
$this->permissions['write'] = 'write';
else {
if ( strpos($permission_result,"C") ) $this->permissions['bind'] = 'bind'; // PUT of new content (i.e. Create)
if ( strpos($permission_result,"D") ) $this->permissions['unbind'] = 'unbind'; // DELETE
if ( strpos($permission_result,"M") ) $this->permissions['write-content'] = 'write-content'; // DELETE
}
}
}
dbg_error_log( "caldav", "Restricted permissions for user accessing someone elses hierarchy: %s", implode( ", ", $this->permissions ) );
}
}
/**
* If the content we are receiving is XML then we parse it here.
*/
$xml_parser = xml_parser_create_ns('UTF-8');
$xml_tags = array();
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parse_into_struct( $xml_parser, $raw_post, $xml_tags );
xml_parser_free($xml_parser);
if ( isset($_SERVER["HTTP_IF_NONE_MATCH"]) ) {
$this->etag_none_match = str_replace('"','',$_SERVER["HTTP_IF_NONE_MATCH"]);
if ( $this->etag_none_match == '' ) unset($this->etag_none_match);
}
if ( isset($_SERVER["HTTP_IF_MATCH"]) ) {
$this->etag_if_match = str_replace('"','',$_SERVER["HTTP_IF_MATCH"]);
if ( $this->etag_if_match == '' ) unset($this->etag_if_match);
}
}
/**
* Are we allowed to do the requested activity
*
* @param string $activity The activity we want to do.
*/
function AllowedTo( $activity ) {
if ( isset($this->permissions['all']) ) return true;
switch( $activity ) {
case 'read':
return isset($this->permissions['read']);
break;
case 'write':
return isset($this->permissions['write']);
break;
case 'delete':
return isset($this->permissions['write']) || isset($this->permissions['unbind']);
break;
case 'create':
return isset($this->permissions['write']) || isset($this->permissions['bind']);
break;
case 'modify':
return isset($this->permissions['write']) || isset($this->permissions['write-content']);
break;
case 'mkcol':
return isset($this->permissions['write']);
break;
}
return false;
}
/**
* Utility function we call when we have a simple status-based response to
* return to the client. Possibly
*
* @param int $status The HTTP status code to send.
* @param string $message The friendly text message to send with the response.
*/
function RequestExitStatus( $status, $message, $content_type="text/plain" ) {
global $session, $c;
switch( $status ) {
case 100: $status_text = "Continue"; break;
case 101: $status_text = "Switching Protocols"; break;
case 200: $status_text = "OK"; break;
case 201: $status_text = "Created"; break;
case 202: $status_text = "Accepted"; break;
case 203: $status_text = "Non-Authoritative Information"; break;
case 204: $status_text = "No Content"; break;
case 205: $status_text = "Reset Content"; break;
case 206: $status_text = "Partial Content"; break;
case 207: $status_text = "Multi-Status"; break;
case 300: $status_text = "Multiple Choices"; break;
case 301: $status_text = "Moved Permanently"; break;
case 302: $status_text = "Found"; break;
case 303: $status_text = "See Other"; break;
case 304: $status_text = "Not Modified"; break;
case 305: $status_text = "Use Proxy"; break;
case 307: $status_text = "Temporary Redirect"; break;
case 400: $status_text = "Bad Request"; break;
case 401: $status_text = "Unauthorized"; break;
case 402: $status_text = "Payment Required"; break;
case 403: $status_text = "Forbidden"; break;
case 404: $status_text = "Not Found"; break;
case 405: $status_text = "Method Not Allowed"; break;
case 406: $status_text = "Not Acceptable"; break;
case 407: $status_text = "Proxy Authentication Required"; break;
case 408: $status_text = "Request Timeout"; break;
case 409: $status_text = "Conflict"; break;
case 410: $status_text = "Gone"; break;
case 411: $status_text = "Length Required"; break;
case 412: $status_text = "Precondition Failed"; break;
case 413: $status_text = "Request Entity Too Large"; break;
case 414: $status_text = "Request-URI Too Long"; break;
case 415: $status_text = "Unsupported Media Type"; break;
case 416: $status_text = "Requested Range Not Satisfiable"; break;
case 417: $status_text = "Expectation Failed"; break;
case 500: $status_text = "Internal Server Error"; break;
case 501: $status_text = "Not Implemented"; break;
}
header( sprintf("HTTP/1.1 %d %s", $status, $status_text) );
header( "Content-type: ".$content_type );
echo $message;
if ( strlen($message) > 100 || strstr($message, "\n") ) {
$message = substr( preg_replace("#\s+#m", ' ', $message ), 0, 100);
}
dbg_error_log("caldav", "Status: %d, Message: %s, User: %d, Path: %s", $status, $message, $session->user_no, $this->path);
exit(0);
}
}
?>

View File

@ -223,7 +223,8 @@
<item url="testing/tests/regression-suite/023-Mulberry-DELETE-3.test" uploadstatus="1" />
<item url="testing/tests/regression-suite/022-Mulberry-PUT-5.test" uploadstatus="1" />
<item url="dba/patches/1.1.5.sql" uploadstatus="1" />
<item url="docs/website/wikiheader.css" />
<item url="htdocs/collection.php" />
<item url="docs/website/wikiheader.css" uploadstatus="1" />
<item url="htdocs/collection.php" uploadstatus="1" />
<item url="inc/CalDAVRequest.php" />
</project>
</webproject>