diff --git a/dba/davical.sql b/dba/davical.sql index 67e7330a..77f19801 100644 --- a/dba/davical.sql +++ b/dba/davical.sql @@ -306,11 +306,13 @@ CREATE INDEX sync_processing_index ON sync_changes( collection_id, dav_id, sync_ CREATE TABLE access_ticket ( ticket_id TEXT PRIMARY KEY, + dav_owner_id INT8 NOT NULL REFERENCES principal(principal_id) ON UPDATE CASCADE ON DELETE CASCADE, is_public BOOLEAN, privileges BIT(24), target_collection_id INT8 NOT NULL REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE, target_resource_id INT8 REFERENCES caldav_data(dav_id) ON UPDATE CASCADE ON DELETE CASCADE, - expires TIMESTAMP + expires TIMESTAMP, + visits INT8 ); diff --git a/dba/patches/1.2.8.sql b/dba/patches/1.2.8.sql index 93b53f15..19298bf8 100644 --- a/dba/patches/1.2.8.sql +++ b/dba/patches/1.2.8.sql @@ -11,11 +11,13 @@ ALTER TABLE caldav_data ADD COLUMN weak_etag TEXT DEFAULT NULL; CREATE TABLE access_ticket ( ticket_id TEXT PRIMARY KEY, + dav_owner_id INT8 NOT NULL REFERENCES principal(principal_id) ON UPDATE CASCADE ON DELETE CASCADE, is_public BOOLEAN, privileges BIT(24), target_collection_id INT8 NOT NULL REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE, target_resource_id INT8 REFERENCES caldav_data(dav_id) ON UPDATE CASCADE ON DELETE CASCADE, - expires TIMESTAMP + expires TIMESTAMP, + visits INT8 ); diff --git a/htdocs/caldav.php b/htdocs/caldav.php index fe1c6cfa..62409783 100644 --- a/htdocs/caldav.php +++ b/htdocs/caldav.php @@ -59,23 +59,24 @@ if ( ! ($request->IsPrincipal() || isset($request->collection) || $request->meth } switch ( $request->method ) { - case 'OPTIONS': include_once('caldav-OPTIONS.php'); break; - case 'REPORT': include_once('caldav-REPORT.php'); break; - case 'PROPFIND': include('caldav-PROPFIND.php'); break; - case 'PUT': include('caldav-PUT.php'); break; - case 'GET': include('caldav-GET.php'); break; - case 'HEAD': include('caldav-GET.php'); break; - case 'PROPPATCH': include('caldav-PROPPATCH.php'); break; - case 'MKCALENDAR': include('caldav-MKCOL.php'); break; - case 'MKCOL': include('caldav-MKCOL.php'); break; - case 'DELETE': include('caldav-DELETE.php'); break; - case 'POST': include('caldav-POST.php'); break; - case 'MOVE': include('caldav-MOVE.php'); break; - case 'ACL': include('caldav-ACL.php'); break; - case 'LOCK': include('caldav-LOCK.php'); break; - case 'UNLOCK': include('caldav-LOCK.php'); break; + case 'OPTIONS': include_once('caldav-OPTIONS.php'); break; + case 'REPORT': include_once('caldav-REPORT.php'); break; + case 'PROPFIND': include('caldav-PROPFIND.php'); break; + case 'PUT': include('caldav-PUT.php'); break; + case 'GET': include('caldav-GET.php'); break; + case 'HEAD': include('caldav-GET.php'); break; + case 'PROPPATCH': include('caldav-PROPPATCH.php'); break; + case 'MKCALENDAR': include('caldav-MKCOL.php'); break; + case 'MKCOL': include('caldav-MKCOL.php'); break; + case 'DELETE': include('caldav-DELETE.php'); break; + case 'POST': include('caldav-POST.php'); break; + case 'MOVE': include('caldav-MOVE.php'); break; + case 'ACL': include('caldav-ACL.php'); break; + case 'LOCK': include('caldav-LOCK.php'); break; + case 'UNLOCK': include('caldav-LOCK.php'); break; + case 'MKTICKET': include('caldav-MKTICKET.php'); break; - case 'TESTRRULE': include('test-RRULE-v2.php'); break; + case 'TESTRRULE': include('test-RRULE-v2.php'); break; default: dbg_error_log( 'caldav', 'Unhandled request method >>%s<<', $request->method ); diff --git a/inc/DAVResource.php b/inc/DAVResource.php index 46c8aeb4..2eeca9d6 100644 --- a/inc/DAVResource.php +++ b/inc/DAVResource.php @@ -534,18 +534,7 @@ EOQRY; if ( !isset($this->privileges) ) $this->FetchPrivileges(); $privilege_names = bits_to_privilege($this->privileges); } - if ( !isset($xmldoc) && isset($GLOBALS['reply']) ) $xmldoc = $GLOBALS['reply']; - $privileges = array(); - foreach( $privilege_names AS $k ) { -// dbg_error_log( 'DAVResource', 'Adding privilege "%s".', $k ); - $privilege = new XMLElement('privilege'); - if ( isset($xmldoc) ) - $xmldoc->NSElement($privilege,$k); - else - $privilege->NewElement($k); - $privileges[] = $privilege; - } - return $privileges; + return privileges_to_XML( $privilege_names, $xmldoc); } @@ -592,6 +581,7 @@ EOQRY; $this->supported_methods['GET'] = ''; $this->supported_methods['PUT'] = ''; $this->supported_methods['HEAD'] = ''; + $this->supported_methods['MKTICKET'] = ''; break; case 'collection': case 'principal': @@ -610,7 +600,8 @@ EOQRY; array( 'GET' => '', 'HEAD' => '', - 'PUT' => '' + 'PUT' => '', + 'MKTICKET' => '' ) ); } diff --git a/inc/always.php b/inc/always.php index 41146349..edf2b587 100644 --- a/inc/always.php +++ b/inc/always.php @@ -469,3 +469,22 @@ function bits_to_privilege( $raw_bits ) { return $out_priv; } + + +/** +* Returns the array of privilege names converted into XMLElements +*/ +function privileges_to_XML( $privilege_names, &$xmldoc=null ) { + if ( !isset($xmldoc) && isset($GLOBALS['reply']) ) $xmldoc = $GLOBALS['reply']; + $privileges = array(); + foreach( $privilege_names AS $k ) { + $privilege = new XMLElement('privilege'); + if ( isset($xmldoc) ) + $xmldoc->NSElement($privilege,$k); + else + $privilege->NewElement($k); + $privileges[] = $privilege; + } + return $privileges; +} + diff --git a/inc/always.php.in b/inc/always.php.in index 41146349..edf2b587 100644 --- a/inc/always.php.in +++ b/inc/always.php.in @@ -469,3 +469,22 @@ function bits_to_privilege( $raw_bits ) { return $out_priv; } + + +/** +* Returns the array of privilege names converted into XMLElements +*/ +function privileges_to_XML( $privilege_names, &$xmldoc=null ) { + if ( !isset($xmldoc) && isset($GLOBALS['reply']) ) $xmldoc = $GLOBALS['reply']; + $privileges = array(); + foreach( $privilege_names AS $k ) { + $privilege = new XMLElement('privilege'); + if ( isset($xmldoc) ) + $xmldoc->NSElement($privilege,$k); + else + $privilege->NewElement($k); + $privileges[] = $privilege; + } + return $privileges; +} + diff --git a/inc/caldav-MKTICKET.php b/inc/caldav-MKTICKET.php new file mode 100644 index 00000000..4c9aee50 --- /dev/null +++ b/inc/caldav-MKTICKET.php @@ -0,0 +1,123 @@ + +* @copyright Morphoss Ltd - http://www.morphoss.com/ +* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later +*/ +dbg_error_log('MKTICKET', 'method handler'); +require_once('AwlQuery.php'); + +$request->NeedPrivilege('DAV::bind'); + +require_once('XMLDocument.php'); +$reply = new XMLDocument(array( 'DAV:' => '', 'T' => 'http://www.xythos.com/namespaces/StorageServer', 'DT' => 'http://xmlns.davical.org/ticket' )); + +$target = new DAVResource( $request->path ); +if ( ! $target->Exists() ) { + $request->XMLResponse( 404, $reply->Render( 'error', new XMLElement('not-found') ) ); +} + +if ( ! isset($request->xml_tags) ) { + $request->XMLResponse( 400, $reply->Render( 'error', new XMLElement('missing-xml-for-request') ) ); +} + +$xmltree = BuildXMLTree( $request->xml_tags, $position); +if ( $xmltree->GetTag() != 'http://www.xythos.com/namespaces/StorageServer:ticketinfo' ) { + $request->XMLResponse( 400, $reply->Render( 'error', new XMLElement('invalid-xml-for-request') ) ); +} + +$ticket_visits = 'infinity'; +$ticket_timeout = 'Seconds-3600'; +$ticket_public = 0; +$ticket_privs_array = array('read-free-busy'); +$ticketinfo = $xmltree->GetContent(); +foreach( $ticketinfo AS $k => $v ) { + // + switch( $v->GetTag() ) { + case 'DAV::timeout': + case 'http://www.xythos.com/namespaces/StorageServer:timeout': + $ticket_timeout = $v->GetContent(); + break; + + case 'DAV::public': + case 'http://xmlns.davical.org/ticket:public': + $ticket_public = 1; + break; + + case 'DAV::visits': + case 'http://www.xythos.com/namespaces/StorageServer:visits': + $ticket_visits = $v->GetContent(); + break; + + case 'DAV::privilege': + case 'http://www.xythos.com/namespaces/StorageServer:privilege': + $ticket_privs_array = $v->GetElements(); // Ensure we always get an array back + $ticket_privileges = 0; + foreach( $ticket_privs_array AS $k1 => $v1 ) { + $ticket_privileges |= privilege_to_bits( $v1->GetTag() ); + } + if ( $ticket_privileges & privilege_to_bits('write') ) $ticket_privileges |= privilege_to_bits( 'read' ); + if ( $ticket_privileges & privilege_to_bits('read') ) $ticket_privileges |= privilege_to_bits( array('read-free-busy', 'read-current-user-privilege-set') ); + if ( $ticket_privileges & privilege_to_bits('read-free-busy') ) $ticket_privileges |= privilege_to_bits( 'schedule-query-freebusy') ); + break; + } +} + +if ( preg_match( '{^([a-z]+)-(\d+)$}', $ticket_timeout, $matches ) ) { + /** It isn't specified, but timeout seems to be 'unit-number' like 'Seconds-3600', so we make it '3600 Seconds' which PostgreSQL understands */ + $sql_timeout = $matches[2] . ' ' . $matches[1]; +} +else { + $sql_timeout = $ticket_timeout; +} + +$sql_visits = ( $ticket_visits == 'infinity' ? -1: intval($ticket_visits) ); + +$collection_id = $target->GetProperty('collection_id'); +$resource_id = $target->GetProperty('dav_id'); + +$i = 0; +do { + $ticket_id = substr(sha1(date('r') .rand(2100000000) . microtime(true)), 7, 8); + $qry = new AwlQuery( + 'INSERT INTO access_ticket ( ticket_id, dav_owner_id, is_public, privileges, target_collection_id, target_resource_id, expires, visits ) + VALUES( :ticket_id, :owner, :public, :privs, :collection, :resource, (current_timestamp + interval :expires), :visits )', + array( + ':ticket_id' => $ticket_id, + ':owner' => $session->principal_id, + ':public' => $ticket_public, + ':privs' => $ticket_privileges, + ':collection' => $collection_id, + ':resource' => $resource_id, + ':expires' => $sql_timeout, + ':visits' => $sql_visits + ) + ) + $result = $qry->Exec('MKTICKET', __LINE__, __FILE__); +} while( !$result && $i++ < 2 ); + + +$ticketinfo = new XMLElement( 'T:ticketinfo', array( + new XMLElement( 'T:id', $ticket_id), + new XMLElement( 'owner', $reply->href( ConstructURL($session->dav_name) ) ), + new XMLElement( 'privilege', privileges_to_XML(bits_to_privilege($ticket_privileges),$reply)), + new XMLElement( 'T:timeout', $ticket_timeout), + new XMLElement( 'T:visits', $ticket_visits) + ) +); +if ( $ticket_public ) $ticketinfo->NewElement( 'DT:public', $ticket_public); + +$request->XMLResponse( 200, $reply->Render( 'prop', new XMLElement('T:ticketdiscovery', $ticketinfo) ) );