/** * Send a need-privileges error response. This function will only return * if the $href is not supplied and the current user has the specified * permission for the request path. * * @param string $privilege The name of the needed privilege. * @param string $href The unconstructed URI where we needed the privilege. */ function NeedPrivilege($privileges, $href = null) { if (is_string($privileges)) { $privileges = array($privileges); } if (!isset($href)) { if ($this->HavePrivilegeTo($privileges)) { return; } $href = $this->path; } $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href(ConstructURL($href)), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], $privileges[0]); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $this->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); exit(0); // Unecessary, but might clarify things }
} break; } } if ($ticket_timeout == 'infinity') { $sql_timeout = null; } else { if (preg_match('{^([a-z]+)-(\\d+)$}i', $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; } } $collection_id = $target->GetProperty('collection_id'); $resource_id = $target->GetProperty('dav_id'); $i = 0; do { $ticket_id = substr(str_replace('/', '', str_replace('+', '', base64_encode(sha1(date('r') . rand(0, 2100000000) . microtime(true), true)))), 7, 8); $qry = new AwlQuery('INSERT INTO access_ticket ( ticket_id, dav_owner_id, privileges, target_collection_id, target_resource_id, expires ) VALUES( :ticket_id, :owner, :privs::INT::BIT(24), :collection, :resource, (current_timestamp + :expires::interval) )', array(':ticket_id' => $ticket_id, ':owner' => $session->principal_id, ':privs' => $ticket_privileges, ':collection' => $collection_id, ':resource' => $resource_id, ':expires' => $sql_timeout)); $result = $qry->Exec('MKTICKET', __LINE__, __FILE__); } while (!$result && $i++ < 2); $privs = new XMLElement('privilege'); foreach (bits_to_privilege($ticket_privileges) as $k => $v) { $reply->NSElement($privs, $v); } $ticketinfo = new XMLElement('T:ticketinfo', array(new XMLElement('T:id', $ticket_id), new XMLElement('owner', $reply->href(ConstructURL('/' . $session->username . '/'))), $privs, new XMLElement('T:timeout', $ticket_timeout), new XMLElement('T:visits', 'infinity'))); $prop = new XMLElement("prop", new XMLElement('T:ticketdiscovery', $ticketinfo), $reply->GetXmlNsArray()); header('Ticket: ' . $ticket_id); $request->XMLResponse(200, $prop);
/** * Deliver scheduling replies to organizer and other attendees * @param vComponent $ical the VCALENDAR to deliver * @return false on error */ function handle_schedule_reply(vCalendar $ical) { global $c, $session, $request; $resources = $ical->GetComponents('VTIMEZONE', false); $ic = $resources[0]; $etag = md5($request->raw_post); $organizer = $ical->GetOrganizer(); // for now we treat events with out organizers as an error if (empty($organizer)) { return false; } $att = $ical->GetAttendees(); $attendees = array_merge($organizer, $att); dbg_error_log("PUT", "Attempting to deliver scheduling request for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/i', '', $attendee->Value()); dbg_error_log("PUT", "Delivering to %s", $attendee_email); $attendee_principal = new DAVPrincipal(array('email' => $attendee_email, 'options' => array('allow_by_email' => true))); $deliver_path = $attendee_principal->internal_url('schedule_inbox'); $attendee_email = preg_replace('/^mailto:/i', '', $attendee->Value()); if ($attendee_email == $request->principal->email) { dbg_error_log("PUT", "not delivering to owner"); continue; } $ar = new DAVResource($deliver_path); if (!$ar->HavePrivilegeTo('schedule-deliver-reply')) { $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href($attendee_principal->url('schedule_inbox')), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], 'schedule-deliver-reply'); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $request->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); continue; } $ncal = new vCalendar(array('METHOD' => 'REPLY')); $ncal->AddComponent(array_merge($ical->GetComponents('VEVENT', false), array($ic))); $content = $ncal->Render(); write_resource(new DAVResource($deliver_path . $etag . '.ics'), $content, $ar, $request->user_no, md5($content), $put_action_type = 'INSERT', $caldav_context = true, $log_action = true, $etag); } $request->DoResponse(201, 'Created'); }
} /** * If we have encountered any instances of failure, the whole damn thing fails. */ if (count($failure) > 0) { $props = array(); $status = array(); foreach ($success as $tag => $v) { // Unfortunately although these succeeded, we failed overall, so they didn't happen... $props[] = new XMLElement($reply->Tag($tag)); } $status[] = new XMLElement('propstat', array(new XMLElement('prop', $props), new XMLElement('status', 'HTTP/1.1 424 Failed Dependency'))); if ($request_type == 'extended-mkcol') { $request->DoResponse($failure_code, $reply->Render('mkcol-response', array_merge($status, $failure), 'text/xml; charset="utf-8"')); } else { array_unshift($failure, $reply->href(ConstructURL($request->path))); $failure[] = new XMLElement('responsedescription', translate('Some properties were not able to be set.')); $request->DoResponse(207, $reply->Render('multistatus', new XMLElement('response', $failure)), 'text/xml; charset="utf-8"'); } } } $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name'; $qry = new AwlQuery($sql, array(':dav_name' => $request->path)); if (!$qry->Exec('MKCOL', __LINE__, __FILE__)) { $request->DoResponse(500, translate('Error querying database.')); } if ($qry->rows() != 0) { $request->DoResponse(405, translate('A collection already exists at that location.')); } $qry = new AwlQuery(); $qry->Begin();
function handle_freebusy_request($ic) { global $c, $session, $request, $ical; $request->NeedPrivilege('CALDAV:schedule-send-freebusy'); $reply = new XMLDocument(array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C")); $responses = array(); $fbq_start = $ic->GetPValue('DTSTART'); $fbq_end = $ic->GetPValue('DTEND'); if (!(isset($fbq_start) || isset($fbq_end))) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND'); } $range_start = new RepeatRuleDateTime($fbq_start); $range_end = new RepeatRuleDateTime($fbq_end); $attendees = $ic->GetProperties('ATTENDEE'); if (preg_match('# iCal/\\d#', $_SERVER['HTTP_USER_AGENT'])) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Responding with free/busy for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Calculating free/busy for %s", $attendee_email); /** @todo Refactor this so we only do one query here and loop through the results */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email); $qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); if (!$qry->Exec('POST', __LINE__, __FILE__)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() > 1) { // Unlikely, but if we get more than one result we'll do an exact match instead. if (!$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() == 0) { /** Sigh... Go back to the original case-insensitive match */ $qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); } } $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); if ($qry->rows() == 0) { $remote = new iSchedule(); $answer = $remote->sendRequest($attendee->Value(), 'VFREEBUSY/REQUEST', $ical->Render()); if ($answer === false) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } foreach ($answer as $a) { if ($a === false) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); } elseif (substr($a, 0, 1) >= 1) { $reply->CalDAVElement($response, "request-status", $a); $reply->CalDAVElement($response, "calendar-data"); } else { $reply->CalDAVElement($response, "request-status", "2.0;Success"); $reply->CalDAVElement($response, "calendar-data", $a); } $responses[] = $response; } continue; } if (!($attendee_usr = $qry->Fetch())) { $request->DoResponse(501, 'Database error'); } if ((privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0) { $reply->CalDAVElement($response, "request-status", "3.8;No authority"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } $attendee_path_match = '^/' . $attendee_usr->username . '/'; $fb = get_freebusy($attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p)); $fb->AddProperty('UID', $ic->GetPValue('UID')); $fb->SetProperties($ic->GetProperties('ORGANIZER'), 'ORGANIZER'); $fb->AddProperty($attendee); $vcal = new vCalendar(array('METHOD' => 'REPLY')); $vcal->AddComponent($fb); $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); $reply->CalDAVElement($response, "request-status", "2.0;Success"); // Cargo-cult setting $reply->CalDAVElement($response, "calendar-data", $vcal->Render()); $responses[] = $response; } $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:caldav'); $request->XMLResponse(200, $response); }
/** * Deliver scheduling replies to organizer and other attendees * @param iCalComponent $ical the VCALENDAR to deliver * @return false on error */ function handle_schedule_reply($ical) { global $c, $session, $request; $resources = $ical->GetComponents('VTIMEZONE', false); $ic = $resources[0]; $etag = md5($request->raw_post); $organizer = $ic->GetProperties('ORGANIZER'); // for now we treat events with out organizers as an error if (count($organizer) < 1) { return false; } $attendees = array_merge($organizer, $ic->GetProperties('ATTENDEE')); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); if (count($wr_attendees) > 0) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Attempting to deliver scheduling request for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Delivering to %s", $attendee_email); $attendee_principal = new CalDAVPrincipal(array('email' => $attendee_email, 'options' => array('allow_by_email' => true))); $deliver_path = preg_replace('/^.*caldav.php/', '', $attendee_principal->schedule_inbox_url); $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); if ($attendee_email == $request->principal->email) { dbg_error_log("POST", "not delivering to owner"); continue; } $ar = new DAVResource($deliver_path); if (!$ar->HavePrivilegeTo('schedule-deliver-reply')) { $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href(ConstructURL($attendee_principal->schedule_inbox_url)), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], 'schedule-deliver-reply'); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $request->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); continue; } $ncal = new iCalComponent(); $ncal->VCalendar(); $ncal->AddProperty('METHOD', 'REPLY'); $ncal->AddComponent(array_merge($ical->GetComponents('VEVENT', false), array($ic))); $content = $ncal->Render(); write_resource($attendee_principal->user_no, $deliver_path . $etag . '.ics', $content, $ar->GetProperty('collection_id'), $request->user_no, md5($content), $ncal, $put_action_type = 'INSERT', $caldav_context = true, $log_action = true, $etag); } $request->DoResponse(201, 'Created'); }
function ischedule_cancel($ic, $attendees, $attendees_fail) { global $c, $session, $request; $reply = new XMLDocument(array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C", "urn:ietf:params:xml:ns:ischedule" => "I")); $responses = array(); $ical = $ic->GetComponents('VEVENT'); $ical = $ical[0]; foreach ($attendees as $k => $attendee) { $XMLresponse = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:ischedule'); dbg_error_log('ischedule', 'scheduling event for ' . $attendee->email); $schedule_target = new Principal('email', $attendee->email); $response = '3.7'; // Attendee was not found on server. if ($schedule_target->Exists()) { $attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar'))); if (!$attendee_calendar->Exists()) { dbg_error_log('ERROR', 'Default calendar at "%s" does not exist for user "%s"', $attendee_calendar->dav_name(), $schedule_target->username()); $response = '5.3;cannot schedule this user, unknown or access denied'; // No scheduling support for user } else { $attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox'))); if (!$attendee_inbox->HavePrivilegeTo('schedule-deliver-invite')) { $response = '3.8;denied'; // No authority to deliver invitations to user. } else { if ($attendee_inbox->WriteCalendarMember($ic, false) !== false) { $response = '2.0;delivered'; // Scheduling invitation delivered successfully } } } } dbg_error_log('PUT', 'Status for attendee <%s> set to "%s"', $attendee->email, $response); $XMLresponse->NewElement("recipient", $reply->href('mailto:' . $attendee->email), false, 'urn:ietf:params:xml:ns:ischedule'); $XMLresponse->NewElement("request-status", $response, false, 'urn:ietf:params:xml:ns:ischedule'); $responses[] = $XMLresponse; } foreach ($attendees_fail as $k => $attendee) { $XMLresponse = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:ischedule'); $XMLresponse->NewElement("recipient", $reply->href('mailto:' . $attendee->email), false, 'urn:ietf:params:xml:ns:ischedule'); $XMLresponse->NewElement("request-status", '5.3;cannot schedule this user, unknown or access denied', false, 'urn:ietf:params:xml:ns:ischedule'); $responses[] = $XMLresponse; } $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:ischedule'); $request->XMLResponse(200, $response); }