diff --git a/dba/davical.sql b/dba/davical.sql index 4f3cfd3f..9c369c69 100644 --- a/dba/davical.sql +++ b/dba/davical.sql @@ -319,6 +319,7 @@ CREATE TABLE dav_binding ( bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY, bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE, access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL, + dav_owner_id INT8 NOT NULL REFERENCES principal(principal_id) ON UPDATE CASCADE ON DELETE CASCADE, parent_container TEXT NOT NULL, dav_name TEXT UNIQUE NOT NULL, dav_displayname TEXT diff --git a/dba/patches/1.2.8.sql b/dba/patches/1.2.8.sql index 3f9b48b4..9a9b6926 100644 --- a/dba/patches/1.2.8.sql +++ b/dba/patches/1.2.8.sql @@ -24,6 +24,7 @@ CREATE TABLE dav_binding ( bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY, bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE, access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL, + dav_owner_id INT8 NOT NULL REFERENCES principal(principal_id) ON UPDATE CASCADE ON DELETE CASCADE, parent_container TEXT NOT NULL, dav_name TEXT UNIQUE NOT NULL, dav_displayname TEXT diff --git a/inc/DAVResource.php b/inc/DAVResource.php index f6803486..949a3839 100644 --- a/inc/DAVResource.php +++ b/inc/DAVResource.php @@ -389,10 +389,12 @@ EOSQL; $sql = <<dav_name(); } + /** + * Returns the dav_name of the resource in our internal namespace + */ + function parent_path() { + if ( $this->IsCollection() ) return $this->collection->parent_container; + return preg_replace( '{[^/]+$}', '', $this->bound_from()); + } + + /** * Returns the principal-URL for this resource @@ -1223,7 +1234,8 @@ EOQRY; case 'DAV::owner': // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner - $reply->DAVElement( $prop, 'owner', $reply->href( $this->principal_url() ) ); + $owner_url = ( isset($this->_is_binding) && $this->_is_binding ? $this->collection->bind_owner_url : $this->principal_url() ); + $reply->DAVElement( $prop, 'owner', $reply->href( $owner_url ) ); break; // Empty tag responses. @@ -1433,24 +1445,24 @@ EOQRY; * * @param array $properties The requested properties for this principal * @param reference $reply A reference to the XMLDocument being used for the reply - * @param boolean $props_only Default false. If true will only return the fragment with the properties, not a full response fragment. * * @return string An XML fragment with the requested properties for this principal */ - function RenderAsXML( $properties, &$reply, $props_only = false ) { + function RenderAsXML( $properties, &$reply, $bound_parent_path = null ) { global $session, $c, $request; dbg_error_log('DAVResource',':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists() ); if ( !$this->Exists() ) return null; - if ( $props_only ) { - dbg_error_log('LOG WARNING','DAVResource::RenderAsXML Called misguidedly - should be call to DAVResource::GetPropStat' ); - return $this->GetPropStat( $properties, $reply, true ); - } - $elements = $this->GetPropStat( $properties, $reply ); - array_unshift( $elements, $reply->href(ConstructURL($this->dav_name))); + if ( isset($bound_parent_path) ) { + $dav_name = str_replace( $this->parent_path(), $bound_parent_path, $this->dav_name ); + } + else + $dav_name = $this->dav_name; + + array_unshift( $elements, $reply->href(ConstructURL($dav_name))); $response = new XMLElement( 'response', $elements ); diff --git a/inc/caldav-BIND.php b/inc/caldav-BIND.php index 2ab8abdd..d57fb051 100644 --- a/inc/caldav-BIND.php +++ b/inc/caldav-BIND.php @@ -65,12 +65,13 @@ if ( $source->IsPrincipal() || !$source->IsCollection() ) { dav_displayname TEXT */ -$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, parent_container, dav_name, dav_displayname ) -VALUES( :target_id, :ticket_id, :parent_container, :dav_name, :displayname )'; +$sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname ) +VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )'; $params = array( ':target_id' => $source->GetProperty('collection_id'), ':ticket_id' => (isset($request->ticket) ? $request->ticket->id() : null), ':parent_container' => $parent->dav_name(), + ':session_principal' => $session->principal_id, ':dav_name' => $destination_path, ':displayname' => 'Bind to '.$source->GetProperty('displayname') ); diff --git a/inc/caldav-PROPFIND.php b/inc/caldav-PROPFIND.php index 45a6c318..23d56d9a 100644 --- a/inc/caldav-PROPFIND.php +++ b/inc/caldav-PROPFIND.php @@ -99,10 +99,11 @@ function add_proxy_response( $which, $parent_path ) { * If '/' is requested, a list of visible users is given, otherwise * a list of calendars for the user which are parented by this path. */ -function get_collection_contents( $depth, $collection ) { +function get_collection_contents( $depth, $collection, $parent_path = null ) { global $c, $session, $request, $reply, $property_list; $bound_from = $collection->bound_from(); + if ( !isset($parent_path) ) $parent_path = $collection->dav_name(); dbg_error_log('PROPFIND','Getting collection contents: Depth %d, Path: %s, Bound from: %s', $depth, $collection->dav_name(), $bound_from ); $date_format = iCalendar::HttpDateFormat(); @@ -132,7 +133,7 @@ function get_collection_contents( $depth, $collection ) { if ( $resource->HavePrivilegeTo('DAV::read') ) { $responses[] = $resource->RenderAsXML($property_list, $reply); if ( $depth > 0 ) { - $responses = array_merge($responses, get_collection_contents( $depth - 1, $resource ) ); + $responses = array_merge($responses, get_collection_contents( $depth - 1, $resource, $binding->dav_name ) ); } } } @@ -152,7 +153,8 @@ function get_collection_contents( $depth, $collection ) { $resource = new DAVResource($subcollection); $responses[] = $resource->RenderAsXML($property_list, $reply); if ( $depth > 0 ) { - $responses = array_merge($responses, get_collection_contents( $depth - 1, $resource ) ); + $responses = array_merge($responses, get_collection_contents( $depth - 1, $resource, + str_replace($resource->parent_path(), $parent_path, $resource->dav_name() ) ) ); } } } @@ -195,7 +197,7 @@ function get_collection_contents( $depth, $collection ) { if( $qry->Exec('PROPFIND',__LINE__,__FILE__) && $qry->rows() > 0 ) { while( $item = $qry->Fetch() ) { $resource = new DAVResource($item); - $responses[] = $resource->RenderAsXML($property_list, $reply); + $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path ); } } } diff --git a/inc/caldav-REPORT-multiget.php b/inc/caldav-REPORT-multiget.php index 7ab50f89..26a9406e 100644 --- a/inc/caldav-REPORT-multiget.php +++ b/inc/caldav-REPORT-multiget.php @@ -56,6 +56,9 @@ switch( $proptype ) { $properties[$propertyname] = 1; } +$collection = new DAVResource($request->path); +$bound_from = $collection->bound_from(); + /** * Build the href list for the IN ( href, href, href, ... ) clause. */ @@ -67,13 +70,13 @@ foreach( $mg_hrefs AS $k => $v ) { * anything up to the matching request->path (which will include any http...) and then * put the $request->path back on. */ - $href = $request->path . preg_replace( "#^.*$request->path#", '', rawurldecode($v->GetContent()) ); + $href = $bound_from . preg_replace( "{^.*\E$request->path\Q}", '', rawurldecode($v->GetContent()) ); dbg_error_log("REPORT", "Reporting on href '%s'", $href ); $href_in .= ($href_in == '' ? '' : ', '); $href_in .= qpg($href); } -$where = " WHERE caldav_data.dav_name ~ ".qpg("^".$request->path)." "; +$where = " WHERE caldav_data.dav_name ~ ".qpg("^".$bound_from)." "; $where .= "AND caldav_data.dav_name IN ( $href_in ) "; $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL "; $where .= "OR (uprivs($session->user_no::int8,calendar_item.user_no,$c->permission_scan_depth::int) = privilege_to_bits('all')) ) "; @@ -86,6 +89,9 @@ if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $where .= $qry = new PgQuery( "SELECT caldav_data.*,calendar_item.* FROM caldav_data INNER JOIN calendar_item USING(dav_id, user_no, dav_name, collection_id) LEFT JOIN collection USING(collection_id)". $where ); if ( $qry->Exec('REPORT',__LINE__,__FILE__) && $qry->rows > 0 ) { while( $calendar_object = $qry->Fetch() ) { + if ( $bound_from != $collection->dav_name() ) { + $calendar_object->dav_name = str_replace( $bound_from, $collection->dav_name(), $calendar_object->dav_name); + } if ( $need_expansion ) { $ics = new iCalComponent($calendar_object->caldav_data); $expanded = expand_event_instances($ics, $expand_range_start, $expand_range_end); diff --git a/inc/caldav-REPORT.php b/inc/caldav-REPORT.php index b6c83c7a..b69b59ad 100644 --- a/inc/caldav-REPORT.php +++ b/inc/caldav-REPORT.php @@ -23,10 +23,9 @@ if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || (isset($c->dbg['repo } } -if ( ! ($request->AllowedTo('read') || $request->AllowedTo('freebusy')) ) { - // The specification states that a lack of privileges MUST result in a 404. RFC4791, Section 7.10 - $request->DoResponse( 404 ); -} +$target = new DAVResource($request->path); + +$target->NeedPrivilege( array('DAV::read', 'urn:ietf:params:xml:ns:caldav:read-free-busy') ); if ( !isset($request->xml_tags) ) { $request->DoResponse( 406, translate("REPORT body contains no XML data!") );