From dc09453a77f33c4dfdff306321ce93cf5fbd2d57 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 8 Feb 2010 11:06:03 -0800 Subject: First steps on converting FeedSub into the pub/sub basis for OStatus communications: * renamed FeedSub plugin to OStatus * now setting avatar on subscriptions * general fixes for subscription * integrated PuSH hub to handle only user timelines on canonical ID url; sends updates directly * set $config['feedsub']['nohub'] = true to test w/ foreign feeds that don't have hubs (won't actually receive updates though) * a few bits of code documentation * HMAC support for verified distributions (safest if sub setup is on HTTPS) And a couple core changes: * minimizing HTML output for exceptions in API requests to aid in debugging * fix for rel=self link in apitimelineuser when id given This does not not yet include any of the individual subscription management (Salmon notifications for sub/unsub, etc) nor a nice UI for user subscriptions. Needs some further cleanup to treat posts as status updates instead of link references. --- plugins/OStatus/actions/hub.php | 176 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 plugins/OStatus/actions/hub.php (limited to 'plugins/OStatus/actions/hub.php') diff --git a/plugins/OStatus/actions/hub.php b/plugins/OStatus/actions/hub.php new file mode 100644 index 000000000..5caf4b48e --- /dev/null +++ b/plugins/OStatus/actions/hub.php @@ -0,0 +1,176 @@ +. + */ + +/** + * Integrated PuSH hub; lets us only ping them what need it. + * @package Hub + * @maintainer Brion Vibber + */ + +/** + + +Things to consider... +* should we purge incomplete subscriptions that never get a verification pingback? +* when can we send subscription renewal checks? + - at next send time probably ok +* when can we handle trimming of subscriptions? + - at next send time probably ok +* should we keep a fail count? + +*/ + + +class HubAction extends Action +{ + function arg($arg, $def=null) + { + // PHP converts '.'s in incoming var names to '_'s. + // It also merges multiple values, which'll break hub.verify and hub.topic for publishing + // @fixme handle multiple args + $arg = str_replace('.', '_', $arg); + return parent::arg($arg, $def); + } + + function prepare($args) + { + StatusNet::setApi(true); // reduce exception reports to aid in debugging + return parent::prepare($args); + } + + function handle() + { + $mode = $this->trimmed('hub.mode'); + switch ($mode) { + case "subscribe": + $this->subscribe(); + break; + case "unsubscribe": + $this->unsubscribe(); + break; + case "publish": + throw new ServerException("Publishing outside feeds not supported.", 400); + default: + throw new ServerException("Unrecognized mode '$mode'.", 400); + } + } + + /** + * Process a PuSH feed subscription request. + * + * HTTP return codes: + * 202 Accepted - request saved and awaiting verification + * 204 No Content - already subscribed + * 403 Forbidden - rejecting this (not specifically spec'd) + */ + function subscribe() + { + $feed = $this->argUrl('hub.topic'); + $callback = $this->argUrl('hub.callback'); + + common_log(LOG_DEBUG, __METHOD__ . ": checking sub'd to $feed $callback"); + if ($this->getSub($feed, $callback)) { + // Already subscribed; return 204 per spec. + header('HTTP/1.1 204 No Content'); + common_log(LOG_DEBUG, __METHOD__ . ': already subscribed'); + return; + } + + common_log(LOG_DEBUG, __METHOD__ . ': setting up'); + $sub = new HubSub(); + $sub->topic = $feed; + $sub->callback = $callback; + $sub->secret = $this->arg('hub.secret', null); + $sub->setLease(intval($this->arg('hub.lease_seconds'))); + + // @fixme check for feeds we don't manage + // @fixme check the verification mode, might want a return immediately? + + common_log(LOG_DEBUG, __METHOD__ . ': inserting'); + $ok = $sub->insert(); + + if (!$ok) { + throw new ServerException("Failed to save subscription record", 500); + } + + // @fixme check errors ;) + + $data = array('sub' => $sub, 'mode' => 'subscribe'); + $qm = QueueManager::get(); + $qm->enqueue($data, 'hubverify'); + + header('HTTP/1.1 202 Accepted'); + common_log(LOG_DEBUG, __METHOD__ . ': done'); + } + + /** + * Process a PuSH feed unsubscription request. + * + * HTTP return codes: + * 202 Accepted - request saved and awaiting verification + * 204 No Content - already subscribed + * 400 Bad Request - invalid params or rejected feed + */ + function unsubscribe() + { + $feed = $this->argUrl('hub.topic'); + $callback = $this->argUrl('hub.callback'); + $sub = $this->getSub($feed, $callback); + + if ($sub) { + if ($sub->verify('unsubscribe')) { + $sub->delete(); + common_log(LOG_INFO, "PuSH unsubscribed $feed for $callback"); + } else { + throw new ServerException("Failed PuSH unsubscription: verification failed! $feed for $callback"); + } + } else { + throw new ServerException("Failed PuSH unsubscription: not subscribed! $feed for $callback"); + } + } + + /** + * Grab and validate a URL from POST parameters. + * @throws ServerException for malformed or non-http/https URLs + */ + protected function argUrl($arg) + { + $url = $this->arg($arg); + $params = array('domain_check' => false, // otherwise breaks my local tests :P + 'allowed_schemes' => array('http', 'https')); + if (Validate::uri($url, $params)) { + return $url; + } else { + throw new ServerException("Invalid URL passed for $arg: '$url'", 400); + } + } + + /** + * Get HubSub subscription record for a given feed & subscriber. + * + * @param string $feed + * @param string $callback + * @return mixed HubSub or false + */ + protected function getSub($feed, $callback) + { + return HubSub::staticGet($feed, $callback); + } +} + -- cgit v1.2.3-54-g00ecf