]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Replace own OMB stack with libomb.
authorAdrian Lang <mail@adrianlang.de>
Mon, 10 Aug 2009 12:48:50 +0000 (14:48 +0200)
committerAdrian Lang <mail@adrianlang.de>
Mon, 10 Aug 2009 12:48:50 +0000 (14:48 +0200)
28 files changed:
actions/accesstoken.php
actions/finishremotesubscribe.php
actions/postnotice.php
actions/remotesubscribe.php
actions/requesttoken.php
actions/updateprofile.php
actions/userauthorization.php
actions/xrds.php
extlib/libomb/base_url_xrds_mapper.php [new file with mode: 0755]
extlib/libomb/constants.php [new file with mode: 0644]
extlib/libomb/datastore.php [new file with mode: 0755]
extlib/libomb/helper.php [new file with mode: 0644]
extlib/libomb/invalidparameterexception.php [new file with mode: 0755]
extlib/libomb/invalidyadisexception.php [new file with mode: 0755]
extlib/libomb/notice.php [new file with mode: 0755]
extlib/libomb/omb_yadis_xrds.php [new file with mode: 0755]
extlib/libomb/plain_xrds_writer.php [new file with mode: 0755]
extlib/libomb/profile.php [new file with mode: 0755]
extlib/libomb/remoteserviceexception.php [new file with mode: 0755]
extlib/libomb/service_consumer.php [new file with mode: 0755]
extlib/libomb/service_provider.php [new file with mode: 0755]
extlib/libomb/unsupportedserviceexception.php [new file with mode: 0755]
extlib/libomb/xrds_mapper.php [new file with mode: 0755]
extlib/libomb/xrds_writer.php [new file with mode: 0755]
lib/oauthstore.php
lib/omb.php
lib/unqueuemanager.php
scripts/ombqueuehandler.php

index 2a8cd17134c84833ae1446fa8957c35ac5e06031..dcd04a1b404f79ad0c8635e080ddd30deea374f3 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Access token class.
+ * Access token class
  *
  * PHP version 5
  *
@@ -32,10 +32,11 @@ if (!defined('LACONICA')) {
     exit(1);
 }
 
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 require_once INSTALLDIR.'/lib/omb.php';
 
 /**
- * Access token class.
+ * Access token class
  *
  * @category Action
  * @package  Laconica
@@ -47,28 +48,23 @@ require_once INSTALLDIR.'/lib/omb.php';
 class AccesstokenAction extends Action
 {
     /**
-     * Class handler.
+     * Class handler
      *
      * @param array $args query arguments
      *
-     * @return boolean false if user doesn't exist
-     */
+     * @return nothing
+     *
+     **/
     function handle($args)
     {
         parent::handle($args);
         try {
-            common_debug('getting request from env variables', __FILE__);
-            common_remove_magic_from_request();
-            $req = OAuthRequest::from_request('POST', common_local_url('accesstoken'));
-            common_debug('getting a server', __FILE__);
-            $server = omb_oauth_server();
-            common_debug('fetching the access token', __FILE__);
-            $token = $server->fetch_access_token($req);
-            common_debug('got this token: "'.print_r($token, true).'"', __FILE__);
-            common_debug('printing the access token', __FILE__);
-            print $token;
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->writeAccessToken();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
         }
     }
 }
+?>
index 5c764aeb0d320605fcd95b5865af89b1965c0ece..13f367823536a3dee0105b4fe9934059bf67af77 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handler for remote subscription finish callback
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
  * Laconica - a distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, Control Yourself, Inc.
  *
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
+ **/
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
+require_once INSTALLDIR.'/lib/omb.php';
 
+/**
+ * Handler for remote subscription finish callback
+ *
+ * When a remote user subscribes a local user, a redirect to this action is
+ * issued after the remote user authorized his service to subscribe.
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
 class FinishremotesubscribeAction extends Action
 {
 
+    /**
+     * Class handler.
+     *
+     * @param array $args query arguments
+     *
+     * @return nothing
+     *
+     **/
     function handle($args)
     {
-
         parent::handle($args);
 
-        if (common_logged_in()) {
-            $this->clientError(_('You can use the local subscription!'));
-            return;
-        }
-
-        $omb = $_SESSION['oauth_authorization_request'];
+        /* Restore session data. RemotesubscribeAction should have stored
+           this entry. */
+        $service  = unserialize($_SESSION['oauth_authorization_request']);
 
-        if (!$omb) {
+        if (!$service) {
             $this->clientError(_('Not expecting this response!'));
             return;
         }
 
-        common_debug('stored request: '.print_r($omb,true), __FILE__);
-
-        common_remove_magic_from_request();
-        $req = OAuthRequest::from_request('POST', common_local_url('finishuserauthorization'));
+        common_debug('stored request: '. print_r($service, true), __FILE__);
 
-        $token = $req->get_parameter('oauth_token');
-
-        # I think this is the success metric
-
-        if ($token != $omb['token']) {
-            $this->clientError(_('Not authorized.'));
-            return;
-        }
-
-        $version = $req->get_parameter('omb_version');
-
-        if ($version != OMB_VERSION_01) {
-            $this->clientError(_('Unknown version of OMB protocol.'));
-            return;
-        }
-
-        $nickname = $req->get_parameter('omb_listener_nickname');
-
-        if (!$nickname) {
-            $this->clientError(_('No nickname provided by remote server.'));
-            return;
-        }
-
-        $profile_url = $req->get_parameter('omb_listener_profile');
-
-        if (!$profile_url) {
-            $this->clientError(_('No profile URL returned by server.'));
-            return;
-        }
-
-        if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) {
-            $this->clientError(_('Invalid profile URL returned by server.'));
-            return;
-        }
-
-        if ($profile_url == common_local_url('showstream', array('nickname' => $nickname))) {
-            $this->clientError(_('You can use the local subscription!'));
-            return;
-        }
-
-        common_debug('listenee: "'.$omb['listenee'].'"', __FILE__);
-
-        $user = User::staticGet('nickname', $omb['listenee']);
+        /* Create user objects for both users. Do it early for request
+           validation. */
+        $listenee = $service->getListeneeURI();
+        $user = User::staticGet('uri', $listenee);
 
         if (!$user) {
             $this->clientError(_('User being listened to doesn\'t exist.'));
             return;
         }
 
-        $other = User::staticGet('uri', $omb['listener']);
+        $other = User::staticGet('uri', $service->getListenerURI());
 
         if ($other) {
             $this->clientError(_('You can use the local subscription!'));
             return;
         }
 
-        $fullname = $req->get_parameter('omb_listener_fullname');
-        $homepage = $req->get_parameter('omb_listener_homepage');
-        $bio = $req->get_parameter('omb_listener_bio');
-        $location = $req->get_parameter('omb_listener_location');
-        $avatar_url = $req->get_parameter('omb_listener_avatar');
-
-        list($newtok, $newsecret) = $this->access_token($omb);
-
-        if (!$newtok || !$newsecret) {
-            $this->clientError(_('Couldn\'t convert request tokens to access tokens.'));
-            return;
-        }
-
-        # XXX: possible attack point; subscribe and return someone else's profile URI
-
-        $remote = Remote_profile::staticGet('uri', $omb['listener']);
-
-        if ($remote) {
-            $exists = true;
-            $profile = Profile::staticGet($remote->id);
-            $orig_remote = clone($remote);
-            $orig_profile = clone($profile);
-            # XXX: compare current postNotice and updateProfile URLs to the ones
-            # stored in the DB to avoid (possibly...) above attack
-        } else {
-            $exists = false;
-            $remote = new Remote_profile();
-            $remote->uri = $omb['listener'];
-            $profile = new Profile();
-        }
-
-        $profile->nickname = $nickname;
-        $profile->profileurl = $profile_url;
-
-        if (!is_null($fullname)) {
-            $profile->fullname = $fullname;
-        }
-        if (!is_null($homepage)) {
-            $profile->homepage = $homepage;
-        }
-        if (!is_null($bio)) {
-            $profile->bio = $bio;
-        }
-        if (!is_null($location)) {
-            $profile->location = $location;
-        }
-
-        if ($exists) {
-            $profile->update($orig_profile);
-        } else {
-            $profile->created = DB_DataObject_Cast::dateTime(); # current time
-            $id = $profile->insert();
-            if (!$id) {
-                $this->serverError(_('Error inserting new profile'));
-                return;
-            }
-            $remote->id = $id;
-        }
-
-        if ($avatar_url) {
-            if (!$this->add_avatar($profile, $avatar_url)) {
-                $this->serverError(_('Error inserting avatar'));
-                return;
-            }
-        }
-
-        $remote->postnoticeurl = $omb['post_notice_url'];
-        $remote->updateprofileurl = $omb['update_profile_url'];
-
-        if ($exists) {
-            if (!$remote->update($orig_remote)) {
-                $this->serverError(_('Error updating remote profile'));
+        /* Perform the handling itself via libomb. */
+        try {
+            $service->finishAuthorization($listenee);
+        } catch (OAuthException $e) {
+            if ($e->getMessage() == 'The authorized token does not equal the ' .
+                                    'submitted token.') {
+                $this->clientError(_('Not authorized.'));
                 return;
-            }
-        } else {
-            $remote->created = DB_DataObject_Cast::dateTime(); # current time
-            if (!$remote->insert()) {
-                $this->serverError(_('Error inserting remote profile'));
+            } else {
+                $this->clientError(_('Couldn\'t convert request token to ' .
+                                     'access token.'));
                 return;
             }
-        }
-
-        if ($user->hasBlocked($profile)) {
-            $this->clientError(_('That user has blocked you from subscribing.'));
+        } catch (OMB_RemoteServiceException $e) {
+            $this->clientError(_('Unknown version of OMB protocol.'));
+            return;
+        } catch (Exception $e) {
+            common_debug('Got exception ' . print_r($e, true), __FILE__);
+            $this->clientError($e->getMessage());
             return;
         }
 
-        $sub = new Subscription();
+        /* The service URLs are not accessible from datastore, so setting them
+           after insertion of the profile. */
+        $remote = Remote_profile::staticGet('uri', $service->getListenerURI());
 
-        $sub->subscriber = $remote->id;
-        $sub->subscribed = $user->id;
+        $orig_remote = clone($remote);
 
-        $sub_exists = false;
+        $remote->postnoticeurl    =
+                            $service->getServiceURI(OMB_ENDPOINT_POSTNOTICE);
+        $remote->updateprofileurl =
+                            $service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE);
 
-        if ($sub->find(true)) {
-            $sub_exists = true;
-            $orig_sub = clone($sub);
-        } else {
-            $sub_exists = false;
-            $sub->created = DB_DataObject_Cast::dateTime(); # current time
-        }
-
-        $sub->token = $newtok;
-        $sub->secret = $newsecret;
-
-        if ($sub_exists) {
-            $result = $sub->update($orig_sub);
-        } else {
-            $result = $sub->insert();
-        }
-
-        if (!$result) {
-            common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
-            $this->clientError(_('Couldn\'t insert new subscription.'));
-            return;
+        if (!$remote->update($orig_remote)) {
+                $this->serverError(_('Error updating remote profile'));
+                return;
         }
 
-        # Notify user, if necessary
-
-        mail_subscribe_notify_profile($user, $profile);
-
-        # Clear the data
+        /* Clear the session data. */
         unset($_SESSION['oauth_authorization_request']);
 
-        # If we show subscriptions in reverse chron order, this should
-        # show up close to the top of the page
-
+        /* If we show subscriptions in reverse chronological order, the new one
+           should show up close to the top of the page. */
         common_redirect(common_local_url('subscribers', array('nickname' =>
                                                              $user->nickname)),
                         303);
     }
-
-    function add_avatar($profile, $url)
-    {
-        $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
-        copy($url, $temp_filename);
-        $imagefile = new ImageFile($profile->id, $temp_filename);
-        $filename = Avatar::filename($profile->id,
-                                     image_type_to_extension($imagefile->type),
-                                     null,
-                                     common_timestamp());
-        rename($temp_filename, Avatar::path($filename));
-        return $profile->setOriginal($filename);
-    }
-
-    function access_token($omb)
-    {
-
-        common_debug('starting request for access token', __FILE__);
-
-        $con = omb_oauth_consumer();
-        $tok = new OAuthToken($omb['token'], $omb['secret']);
-
-        common_debug('using request token "'.$tok.'"', __FILE__);
-
-        $url = $omb['access_token_url'];
-
-        common_debug('using access token url "'.$url.'"', __FILE__);
-
-        # XXX: Is this the right thing to do? Strip off GET params and make them
-        # POST params? Seems wrong to me.
-
-        $parsed = parse_url($url);
-        $params = array();
-        parse_str($parsed['query'], $params);
-
-        $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params);
-
-        $req->set_parameter('omb_version', OMB_VERSION_01);
-
-        # XXX: test to see if endpoint accepts this signature method
-
-        $req->sign_request(omb_hmac_sha1(), $con, $tok);
-
-        # We re-use this tool's fetcher, since it's pretty good
-
-        common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__);
-        common_debug('posting request data "'.$req->to_postdata().'"', __FILE__);
-
-        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-        $result = $fetcher->post($req->get_normalized_http_url(),
-                                 $req->to_postdata(),
-                                 array('User-Agent: Laconica/' . LACONICA_VERSION));
-
-        common_debug('got result: "'.print_r($result,true).'"', __FILE__);
-
-        if ($result->status != 200) {
-            return null;
-        }
-
-        parse_str($result->body, $return);
-
-        return array($return['oauth_token'], $return['oauth_token_secret']);
-    }
 }
index eb2d63b61cf08119ba2911b130a6aaf50ee508fd..74be47119a4d330204f99207f891594e1d254221 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handle postnotice action
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
  * Laconica - a distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, Control Yourself, Inc.
  *
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 
+/**
+ * Handler for postnotice action
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
 class PostnoticeAction extends Action
 {
     function handle($args)
     {
         parent::handle($args);
+        if (!$this->checkNotice()) {
+            return;
+        }
         try {
-            common_remove_magic_from_request();
-            $req = OAuthRequest::from_request('POST', common_local_url('postnotice'));
-            # Note: server-to-server function!
-            $server = omb_oauth_server();
-            list($consumer, $token) = $server->verify_request($req);
-            if ($this->save_notice($req, $consumer, $token)) {
-                print "omb_version=".OMB_VERSION_01;
-            }
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->handlePostNotice();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
             return;
         }
     }
 
-    function save_notice(&$req, &$consumer, &$token)
+    function checkNotice()
     {
-        $version = $req->get_parameter('omb_version');
-        if ($version != OMB_VERSION_01) {
-            $this->clientError(_('Unsupported OMB version'), 400);
-            return false;
-        }
-        # First, check to see
-        $listenee =  $req->get_parameter('omb_listenee');
-        $remote_profile = Remote_profile::staticGet('uri', $listenee);
-        if (!$remote_profile) {
-            $this->clientError(_('Profile unknown'), 403);
-            return false;
-        }
-        $sub = Subscription::staticGet('token', $token->key);
-        if (!$sub) {
-            $this->clientError(_('No such subscription'), 403);
-            return false;
-        }
-        $content = $req->get_parameter('omb_notice_content');
-        $content_shortened = common_shorten_links($content);
-        if (mb_strlen($content_shortened) > 140) {
+        $content = common_shorten_links($_POST['omb_notice_content']);
+        if (mb_strlen($content) > 140) {
             $this->clientError(_('Invalid notice content'), 400);
             return false;
         }
-        $notice_uri = $req->get_parameter('omb_notice');
-        if (!Validate::uri($notice_uri) &&
-            !common_valid_tag($notice_uri)) {
-            $this->clientError(_('Invalid notice uri'), 400);
-            return false;
-        }
-        $notice_url = $req->get_parameter('omb_notice_url');
-        if ($notice_url && !common_valid_http_url($notice_url)) {
-            $this->clientError(_('Invalid notice url'), 400);
-            return false;
-        }
-        $notice = Notice::staticGet('uri', $notice_uri);
-        if (!$notice) {
-            $notice = Notice::saveNew($remote_profile->id, $content, 'omb', false, null, $notice_uri);
-            if (is_string($notice)) {
-                common_server_serror($notice, 500);
-                return false;
-            }
-            common_broadcast_notice($notice, true);
-        }
         return true;
     }
 }
+?>
index e658f8d3748ed290159222c5ed13867187e3be3e..5122c117287567b24609d1bb641fcd41109483bf 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handler for remote subscription
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
  * Laconica - a distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, Control Yourself, Inc.
  *
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
+ **/
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
+require_once INSTALLDIR.'/extlib/libomb/profile.php';
 
-require_once(INSTALLDIR.'/lib/omb.php');
+/**
+ * Handler for remote subscription
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
 
 class RemotesubscribeAction extends Action
 {
@@ -36,7 +62,7 @@ class RemotesubscribeAction extends Action
             return false;
         }
 
-        $this->nickname = $this->trimmed('nickname');
+        $this->nickname    = $this->trimmed('nickname');
         $this->profile_url = $this->trimmed('profile_url');
 
         return true;
@@ -47,7 +73,7 @@ class RemotesubscribeAction extends Action
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            # CSRF protection
+            /* Use a session token for CSRF protection. */
             $token = $this->trimmed('token');
             if (!$token || $token != common_session_token()) {
                 $this->showForm(_('There was a problem with your session token. '.
@@ -90,8 +116,8 @@ class RemotesubscribeAction extends Action
 
     function showContent()
     {
-        # id = remotesubscribe conflicts with the
-        # button on profile page
+        /* The id 'remotesubscribe' conflicts with the
+           button on profile page. */
         $this->elementStart('form', array('id' => 'form_remote_subscribe',
                                           'method' => 'post',
                                           'class' => 'form_settings',
@@ -117,13 +143,13 @@ class RemotesubscribeAction extends Action
 
     function remoteSubscription()
     {
-        $user = $this->getUser();
-
-        if (!$user) {
+        if (!$this->nickname) {
             $this->showForm(_('No such user.'));
             return;
         }
 
+        $user = User::staticGet('nickname', $this->nickname);
+
         $this->profile_url = $this->trimmed('profile_url');
 
         if (!$this->profile_url) {
@@ -131,233 +157,37 @@ class RemotesubscribeAction extends Action
             return;
         }
 
-        if (!Validate::uri($this->profile_url, array('allowed_schemes' => array('http', 'https')))) {
+        if (!Validate::uri($this->profile_url,
+                           array('allowed_schemes' => array('http', 'https')))) {
             $this->showForm(_('Invalid profile URL (bad format)'));
             return;
         }
 
-        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-        $yadis = Auth_Yadis_Yadis::discover($this->profile_url, $fetcher);
-
-        if (!$yadis || $yadis->failed) {
-            $this->showForm(_('Not a valid profile URL (no YADIS document).'));
-            return;
-        }
-
-        # XXX: a little liberal for sites that accidentally put whitespace before the xml declaration
-
-        $xrds =& Auth_Yadis_XRDS::parseXRDS(trim($yadis->response_text));
-
-        if (!$xrds) {
-            $this->showForm(_('Not a valid profile URL (no XRDS defined).'));
-            return;
-        }
-
-        $omb = $this->getOmb($xrds);
-
-        if (!$omb) {
-            $this->showForm(_('Not a valid profile URL (incorrect services).'));
-            return;
-        }
-
-        if (omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]) ==
-            common_local_url('requesttoken'))
-        {
-            $this->showForm(_('That\'s a local profile! Login to subscribe.'));
+        try {
+            $service = new OMB_Service_Consumer($this->profile_url,
+                                                common_root_url(),
+                                                omb_oauth_datastore());
+        } catch (OMB_InvalidYadisException $e) {
+            $this->showForm(_('Not a valid profile URL (no YADIS document or ' .
+                              'no or invalid XRDS defined).'));
             return;
         }
 
-        if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) {
+        if ($service->getServiceURI(OAUTH_ENDPOINT_REQUEST) ==
+            common_local_url('requesttoken') ||
+            User::staticGet('uri', $service->getRemoteUserURI())) {
             $this->showForm(_('That\'s a local profile! Login to subscribe.'));
             return;
         }
 
-        list($token, $secret) = $this->requestToken($omb);
-
-        if (!$token || !$secret) {
+        try {
+            $service->requestToken();
+        } catch (OMB_RemoteServiceException $e) {
             $this->showForm(_('Couldn\'t get a request token.'));
             return;
         }
 
-        $this->requestAuthorization($user, $omb, $token, $secret);
-    }
-
-    function getUser()
-    {
-        $user = null;
-        if ($this->nickname) {
-            $user = User::staticGet('nickname', $this->nickname);
-        }
-        return $user;
-    }
-
-    function getOmb($xrds)
-    {
-        static $omb_endpoints = array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
-        static $oauth_endpoints = array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE,
-                                        OAUTH_ENDPOINT_ACCESS);
-        $omb = array();
-
-        # XXX: the following code could probably be refactored to eliminate dupes
-
-        $oauth_services = omb_get_services($xrds, OAUTH_DISCOVERY);
-
-        if (!$oauth_services) {
-            return null;
-        }
-
-        $oauth_service = $oauth_services[0];
-
-        $oauth_xrd = $this->getXRD($oauth_service, $xrds);
-
-        if (!$oauth_xrd) {
-            return null;
-        }
-
-        if (!$this->addServices($oauth_xrd, $oauth_endpoints, $omb)) {
-            return null;
-        }
-
-        $omb_services = omb_get_services($xrds, OMB_NAMESPACE);
-
-        if (!$omb_services) {
-            return null;
-        }
-
-        $omb_service = $omb_services[0];
-
-        $omb_xrd = $this->getXRD($omb_service, $xrds);
-
-        if (!$omb_xrd) {
-            return null;
-        }
-
-        if (!$this->addServices($omb_xrd, $omb_endpoints, $omb)) {
-            return null;
-        }
-
-        # XXX: check that we got all the services we needed
-
-        foreach (array_merge($omb_endpoints, $oauth_endpoints) as $type) {
-            if (!array_key_exists($type, $omb) || !$omb[$type]) {
-                return null;
-            }
-        }
-
-        if (!omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])) {
-            return null;
-        }
-
-        return $omb;
-    }
-
-    function getXRD($main_service, $main_xrds)
-    {
-        $uri = omb_service_uri($main_service);
-        if (strpos($uri, "#") !== 0) {
-            # FIXME: more rigorous handling of external service definitions
-            return null;
-        }
-        $id = substr($uri, 1);
-        $nodes = $main_xrds->allXrdNodes;
-        $parser = $main_xrds->parser;
-        foreach ($nodes as $node) {
-            $attrs = $parser->attributes($node);
-            if (array_key_exists('xml:id', $attrs) &&
-                $attrs['xml:id'] == $id) {
-                # XXX: trick the constructor into thinking this is the only node
-                $bogus_nodes = array($node);
-                return new Auth_Yadis_XRDS($parser, $bogus_nodes);
-            }
-        }
-        return null;
-    }
-
-    function addServices($xrd, $types, &$omb)
-    {
-        foreach ($types as $type) {
-            $matches = omb_get_services($xrd, $type);
-            if ($matches) {
-                $omb[$type] = $matches[0];
-            } else {
-                # no match for type
-                return false;
-            }
-        }
-        return true;
-    }
-
-    function requestToken($omb)
-    {
-        $con = omb_oauth_consumer();
-
-        $url = omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]);
-
-        # XXX: Is this the right thing to do? Strip off GET params and make them
-        # POST params? Seems wrong to me.
-
-        $parsed = parse_url($url);
-        $params = array();
-        parse_str($parsed['query'], $params);
-
-        $req = OAuthRequest::from_consumer_and_token($con, null, "POST", $url, $params);
-
-        $listener = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
-
-        if (!$listener) {
-            return null;
-        }
-
-        $req->set_parameter('omb_listener', $listener);
-        $req->set_parameter('omb_version', OMB_VERSION_01);
-
-        # XXX: test to see if endpoint accepts this signature method
-
-        $req->sign_request(omb_hmac_sha1(), $con, null);
-
-        # We re-use this tool's fetcher, since it's pretty good
-
-        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
-
-        $result = $fetcher->post($req->get_normalized_http_url(),
-                                 $req->to_postdata(),
-                                 array('User-Agent: Laconica/' . LACONICA_VERSION));
-        if ($result->status != 200) {
-            return null;
-        }
-
-        parse_str($result->body, $return);
-
-        return array($return['oauth_token'], $return['oauth_token_secret']);
-    }
-
-    function requestAuthorization($user, $omb, $token, $secret)
-    {
-        $con = omb_oauth_consumer();
-        $tok = new OAuthToken($token, $secret);
-
-        $url = omb_service_uri($omb[OAUTH_ENDPOINT_AUTHORIZE]);
-
-        # XXX: Is this the right thing to do? Strip off GET params and make them
-        # POST params? Seems wrong to me.
-
-        $parsed = parse_url($url);
-        $params = array();
-        parse_str($parsed['query'], $params);
-
-        $req = OAuthRequest::from_consumer_and_token($con, $tok, 'GET', $url, $params);
-
-        # We send over a ton of information. This lets the other
-        # server store info about our user, and it lets the current
-        # user decide if they really want to authorize the subscription.
-
-        $req->set_parameter('omb_version', OMB_VERSION_01);
-        $req->set_parameter('omb_listener', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]));
-        $req->set_parameter('omb_listenee', $user->uri);
-        $req->set_parameter('omb_listenee_profile', common_profile_url($user->nickname));
-        $req->set_parameter('omb_listenee_nickname', $user->nickname);
-        $req->set_parameter('omb_listenee_license', common_config('license', 'url'));
-
+        /* Create an OMB_Profile from $user. */
         $profile = $user->getProfile();
         if (!$profile) {
             common_log_db_error($user, 'SELECT', __FILE__);
@@ -365,49 +195,16 @@ class RemotesubscribeAction extends Action
             return;
         }
 
-        if (!is_null($profile->fullname)) {
-            $req->set_parameter('omb_listenee_fullname', $profile->fullname);
-        }
-        if (!is_null($profile->homepage)) {
-            $req->set_parameter('omb_listenee_homepage', $profile->homepage);
-        }
-        if (!is_null($profile->bio)) {
-            $req->set_parameter('omb_listenee_bio', $profile->bio);
-        }
-        if (!is_null($profile->location)) {
-            $req->set_parameter('omb_listenee_location', $profile->location);
-        }
-        $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
-        if ($avatar) {
-            $req->set_parameter('omb_listenee_avatar', $avatar->url);
-        }
-
-        # XXX: add a nonce to prevent replay attacks
-
-        $req->set_parameter('oauth_callback', common_local_url('finishremotesubscribe'));
-
-        # XXX: test to see if endpoint accepts this signature method
-
-        $req->sign_request(omb_hmac_sha1(), $con, $tok);
-
-        # store all our info here
-
-        $omb['listenee'] = $user->nickname;
-        $omb['listener'] = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
-        $omb['token'] = $token;
-        $omb['secret'] = $secret;
-        # call doesn't work after bounce back so we cache; maybe serialization issue...?
-        $omb['access_token_url'] = omb_service_uri($omb[OAUTH_ENDPOINT_ACCESS]);
-        $omb['post_notice_url'] = omb_service_uri($omb[OMB_ENDPOINT_POSTNOTICE]);
-        $omb['update_profile_url'] = omb_service_uri($omb[OMB_ENDPOINT_UPDATEPROFILE]);
+        $target_url = $service->requestAuthorization(
+                                   profile_to_omb_profile($user->uri, $profile),
+                                   common_local_url('finishremotesubscribe'));
 
         common_ensure_session();
 
-        $_SESSION['oauth_authorization_request'] = $omb;
-
-        # Redirect to authorization service
+        $_SESSION['oauth_authorization_request'] = serialize($service);
 
-        common_redirect($req->to_url(), 303);
-        return;
+        /* Redirect to the remote service for authorization. */
+        common_redirect($target_url, 303);
     }
 }
+?>
index 8d1e3f004342ea399a9a35efb80386b57063bc58..8328962f2d5a94ec5aab2417df728995263eb740 100644 (file)
@@ -34,6 +34,7 @@ if (!defined('LACONICA')) {
 }
 
 require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 
 /**
  * Request token action class.
@@ -49,17 +50,17 @@ class RequesttokenAction extends Action
 {
      /**
      * Is read only?
-     * 
+     *
      * @return boolean false
      */
-    function isReadOnly($args)
+    function isReadOnly()
     {
         return false;
     }
-    
+
     /**
      * Class handler.
-     * 
+     *
      * @param array $args array of arguments
      *
      * @return void
@@ -68,14 +69,12 @@ class RequesttokenAction extends Action
     {
         parent::handle($args);
         try {
-            common_remove_magic_from_request();
-            $req    = OAuthRequest::from_request('POST', common_local_url('requesttoken'));
-            $server = omb_oauth_server();
-            $token  = $server->fetch_request_token($req);
-            print $token;
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->writeRequestToken();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
         }
     }
 }
-
+?>
index d8b62fb09051d59f55c5ad374b6937dec2f3b0c0..345c28b8d75f9271cc4a656f862005fbf1a82717 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Handle an updateprofile action
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
  * Laconica - a distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, Control Yourself, Inc.
  *
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
 
+/**
+ * Handle an updateprofile action
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
 class UpdateprofileAction extends Action
 {
-    
+
     function handle($args)
     {
         parent::handle($args);
         try {
-            common_remove_magic_from_request();
-            $req = OAuthRequest::from_request('POST', common_local_url('updateprofile'));
-            # Note: server-to-server function!
-            $server = omb_oauth_server();
-            list($consumer, $token) = $server->verify_request($req);
-            if ($this->update_profile($req, $consumer, $token)) {
-                header('HTTP/1.1 200 OK');
-                header('Content-type: text/plain');
-                print "omb_version=".OMB_VERSION_01;
-            }
-        } catch (OAuthException $e) {
+            $srv = new OMB_Service_Provider(null, omb_oauth_datastore(),
+                                            omb_oauth_server());
+            $srv->handleUpdateProfile();
+        } catch (Exception $e) {
             $this->serverError($e->getMessage());
             return;
         }
     }
-
-    function update_profile($req, $consumer, $token)
-    {
-        $version = $req->get_parameter('omb_version');
-        if ($version != OMB_VERSION_01) {
-            $this->clientError(_('Unsupported OMB version'), 400);
-            return false;
-        }
-        # First, check to see if listenee exists
-        $listenee =  $req->get_parameter('omb_listenee');
-        $remote = Remote_profile::staticGet('uri', $listenee);
-        if (!$remote) {
-            $this->clientError(_('Profile unknown'), 404);
-            return false;
-        }
-        # Second, check to see if they should be able to post updates!
-        # We see if there are any subscriptions to that remote user with
-        # the given token.
-
-        $sub = new Subscription();
-        $sub->subscribed = $remote->id;
-        $sub->token = $token->key;
-        if (!$sub->find(true)) {
-            $this->clientError(_('You did not send us that profile'), 403);
-            return false;
-        }
-
-        $profile = Profile::staticGet('id', $remote->id);
-        if (!$profile) {
-            # This one is our fault
-            $this->serverError(_('Remote profile with no matching profile'), 500);
-            return false;
-        }
-        $nickname = $req->get_parameter('omb_listenee_nickname');
-        if ($nickname && !Validate::string($nickname, array('min_length' => 1,
-                                                            'max_length' => 64,
-                                                            'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
-            $this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.'));
-            return false;
-        }
-        $license = $req->get_parameter('omb_listenee_license');
-        if ($license && !common_valid_http_url($license)) {
-            $this->clientError(sprintf(_("Invalid license URL '%s'"), $license));
-            return false;
-        }
-        $profile_url = $req->get_parameter('omb_listenee_profile');
-        if ($profile_url && !common_valid_http_url($profile_url)) {
-            $this->clientError(sprintf(_("Invalid profile URL '%s'."), $profile_url));
-            return false;
-        }
-        # optional stuff
-        $fullname = $req->get_parameter('omb_listenee_fullname');
-        if ($fullname && mb_strlen($fullname) > 255) {
-            $this->clientError(_("Full name is too long (max 255 chars)."));
-            return false;
-        }
-        $homepage = $req->get_parameter('omb_listenee_homepage');
-        if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
-            $this->clientError(sprintf(_("Invalid homepage '%s'"), $homepage));
-            return false;
-        }
-        $bio = $req->get_parameter('omb_listenee_bio');
-        if ($bio && mb_strlen($bio) > 140) {
-            $this->clientError(_("Bio is too long (max 140 chars)."));
-            return false;
-        }
-        $location = $req->get_parameter('omb_listenee_location');
-        if ($location && mb_strlen($location) > 255) {
-            $this->clientError(_("Location is too long (max 255 chars)."));
-            return false;
-        }
-        $avatar = $req->get_parameter('omb_listenee_avatar');
-        if ($avatar) {
-            if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
-                $this->clientError(sprintf(_("Invalid avatar URL '%s'"), $avatar));
-                return false;
-            }
-            $size = @getimagesize($avatar);
-            if (!$size) {
-                $this->clientError(sprintf(_("Can't read avatar URL '%s'"), $avatar));
-                return false;
-            }
-            if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
-                $this->clientError(sprintf(_("Wrong size image at '%s'"), $avatar));
-                return false;
-            }
-            if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
-                                          IMAGETYPE_PNG))) {
-                $this->clientError(sprintf(_("Wrong image type for '%s'"), $avatar));
-                return false;
-            }
-        }
-
-        $orig_profile = clone($profile);
-
-        /* Use values even if they are an empty string. Parsing an empty string in
-           updateProfile is the specified way of clearing a parameter in OMB. */
-        if (!is_null($nickname)) {
-            $profile->nickname = $nickname;
-        }
-        if (!is_null($profile_url)) {
-            $profile->profileurl = $profile_url;
-        }
-        if (!is_null($fullname)) {
-            $profile->fullname = $fullname;
-        }
-        if (!is_null($homepage)) {
-            $profile->homepage = $homepage;
-        }
-        if (!is_null($bio)) {
-            $profile->bio = $bio;
-        }
-        if (!is_null($location)) {
-            $profile->location = $location;
-        }
-
-        if (!$profile->update($orig_profile)) {
-            $this->serverError(_('Could not save new profile info'), 500);
-            return false;
-        } else {
-            if ($avatar) {
-                $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
-                copy($avatar, $temp_filename);
-                $imagefile = new ImageFile($profile->id, $temp_filename);
-                $filename = Avatar::filename($profile->id,
-                                     image_type_to_extension($imagefile->type),
-                                     null,
-                                     common_timestamp());
-                rename($temp_filename, Avatar::path($filename));
-                if (!$profile->setOriginal($filename)) {
-                    $this->serverError(_('Could not save avatar info'), 500);
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
 }
+?>
index 8dc2c808d6ff849f0da742e342de137203eeacd4..d5b6a69986a7edfdeb61a55b3cefa55f43352856 100644 (file)
@@ -1,5 +1,16 @@
 <?php
-/*
+/**
+ * Let the user authorize a remote subscription request
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
  * Laconica - a distributed open-source microblogging tool
  * Copyright (C) 2008, 2009, Control Yourself, Inc.
  *
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
+require_once INSTALLDIR.'/extlib/libomb/profile.php';
 define('TIMESTAMP_THRESHOLD', 300);
 
 class UserauthorizationAction extends Action
@@ -32,42 +47,58 @@ class UserauthorizationAction extends Action
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            # CSRF protection
+            /* Use a session token for CSRF protection. */
             $token = $this->trimmed('token');
             if (!$token || $token != common_session_token()) {
-                $params = $this->getStoredParams();
-                $this->showForm($params, _('There was a problem with your session token. '.
-                                        'Try again, please.'));
+                $srv = $this->getStoredParams();
+                $this->showForm($srv->getRemoteUser(), _('There was a problem ' .
+                                        'with your session token. Try again, ' .
+                                        'please.'));
                 return;
             }
-            # We've shown the form, now post user's choice
+            /* We've shown the form, now post user's choice. */
             $this->sendAuthorization();
         } else {
             if (!common_logged_in()) {
-                # Go log in, and then come back
+                /* Go log in, and then come back. */
                 common_set_returnto($_SERVER['REQUEST_URI']);
 
                 common_redirect(common_local_url('login'));
                 return;
             }
 
+            $user    = common_current_user();
+            $profile = $user->getProfile();
+            if (!$profile) {
+                common_log_db_error($user, 'SELECT', __FILE__);
+                $this->serverError(_('User without matching profile'));
+                return;
+            }
+
+            /* TODO: If no token is passed the user should get a prompt to enter
+               it according to OAuth Core 1.0. */
             try {
-                $this->validateRequest();
-                $this->storeParams($_GET);
-                $this->showForm($_GET);
-            } catch (OAuthException $e) {
+                $this->validateOmb();
+                $srv = new OMB_Service_Provider(
+                        profile_to_omb_profile($_GET['omb_listener'], $profile),
+                        omb_oauth_datastore());
+
+                $remote_user = $srv->handleUserAuth();
+            } catch (Exception $e) {
                 $this->clearParams();
                 $this->clientError($e->getMessage());
                 return;
             }
 
+            $this->storeParams($srv);
+            $this->showForm($remote_user);
         }
     }
 
     function showForm($params, $error=null)
     {
         $this->params = $params;
-        $this->error = $error;
+        $this->error  = $error;
         $this->showPage();
     }
 
@@ -79,23 +110,24 @@ class UserauthorizationAction extends Action
     function showPageNotice()
     {
         $this->element('p', null, _('Please check these details to make sure '.
-                                    'that you want to subscribe to this user\'s notices. '.
-                                    'If you didn\'t just ask to subscribe to someone\'s notices, '.
-                                    'click "Reject".'));
+                                    'that you want to subscribe to this ' .
+                                    'user\'s notices. If you didn\'t just ask ' .
+                                    'to subscribe to someone\'s notices, '.
+                                    'click “Reject”.'));
     }
 
     function showContent()
     {
         $params = $this->params;
 
-        $nickname = $params['omb_listenee_nickname'];
-        $profile = $params['omb_listenee_profile'];
-        $license = $params['omb_listenee_license'];
-        $fullname = $params['omb_listenee_fullname'];
-        $homepage = $params['omb_listenee_homepage'];
-        $bio = $params['omb_listenee_bio'];
-        $location = $params['omb_listenee_location'];
-        $avatar = $params['omb_listenee_avatar'];
+        $nickname = $params->getNickname();
+        $profile  = $params->getProfileURL();
+        $license  = $params->getLicenseURL();
+        $fullname = $params->getFullname();
+        $homepage = $params->getHomepage();
+        $bio      = $params->getBio();
+        $location = $params->getLocation();
+        $avatar   = $params->getAvatarURL();
 
         $this->elementStart('div', array('class' => 'profile'));
         $this->elementStart('div', 'entity_profile vcard');
@@ -172,11 +204,14 @@ class UserauthorizationAction extends Action
                                           'id' => 'userauthorization',
                                           'class' => 'form_user_authorization',
                                           'name' => 'userauthorization',
-                                          'action' => common_local_url('userauthorization')));
+                                          'action' => common_local_url(
+                                                         'userauthorization')));
         $this->hidden('token', common_session_token());
 
-        $this->submit('accept', _('Accept'), 'submit accept', null, _('Subscribe to this user'));
-        $this->submit('reject', _('Reject'), 'submit reject', null, _('Reject this subscription'));
+        $this->submit('accept', _('Accept'), 'submit accept', null,
+                      _('Subscribe to this user'));
+        $this->submit('reject', _('Reject'), 'submit reject', null,
+                      _('Reject this subscription'));
         $this->elementEnd('form');
         $this->elementEnd('li');
         $this->elementEnd('ul');
@@ -186,191 +221,27 @@ class UserauthorizationAction extends Action
 
     function sendAuthorization()
     {
-        $params = $this->getStoredParams();
+        $srv = $this->getStoredParams();
 
-        if (!$params) {
+        if (is_null($srv)) {
             $this->clientError(_('No authorization request!'));
             return;
         }
 
-        $callback = $params['oauth_callback'];
-
-        if ($this->arg('accept')) {
-            if (!$this->authorizeToken($params)) {
-                $this->clientError(_('Error authorizing token'));
-            }
-            if (!$this->saveRemoteProfile($params)) {
-                $this->clientError(_('Error saving remote profile'));
-            }
-            if (!$callback) {
-                $this->showAcceptMessage($params['oauth_token']);
-            } else {
-                $newparams = array();
-                $newparams['oauth_token'] = $params['oauth_token'];
-                $newparams['omb_version'] = OMB_VERSION_01;
-                $user = User::staticGet('uri', $params['omb_listener']);
-                $profile = $user->getProfile();
-                if (!$profile) {
-                    common_log_db_error($user, 'SELECT', __FILE__);
-                    $this->serverError(_('User without matching profile'));
-                    return;
-                }
-                $newparams['omb_listener_nickname'] = $user->nickname;
-                $newparams['omb_listener_profile'] = common_local_url('showstream',
-                                                                   array('nickname' => $user->nickname));
-                if (!is_null($profile->fullname)) {
-                    $newparams['omb_listener_fullname'] = $profile->fullname;
-                }
-                if (!is_null($profile->homepage)) {
-                    $newparams['omb_listener_homepage'] = $profile->homepage;
-                }
-                if (!is_null($profile->bio)) {
-                    $newparams['omb_listener_bio'] = $profile->bio;
-                }
-                if (!is_null($profile->location)) {
-                    $newparams['omb_listener_location'] = $profile->location;
-                }
-                $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
-                if ($avatar) {
-                    $newparams['omb_listener_avatar'] = $avatar->url;
-                }
-                $parts = array();
-                foreach ($newparams as $k => $v) {
-                    $parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v);
-                }
-                $query_string = implode('&', $parts);
-                $parsed = parse_url($callback);
-                $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string;
-                common_redirect($url, 303);
-            }
-        } else {
-            if (!$callback) {
-                $this->showRejectMessage();
-            } else {
-                # XXX: not 100% sure how to signal failure... just redirect without token?
-                common_redirect($callback, 303);
-            }
-        }
-    }
-
-    function authorizeToken(&$params)
-    {
-        $token_field = $params['oauth_token'];
-        $rt = new Token();
-        $rt->tok = $token_field;
-        $rt->type = 0;
-        $rt->state = 0;
-        if ($rt->find(true)) {
-            $orig_rt = clone($rt);
-            $rt->state = 1; # Authorized but not used
-            if ($rt->update($orig_rt)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    # XXX: refactor with similar code in finishremotesubscribe.php
-
-    function saveRemoteProfile(&$params)
-    {
-        # FIXME: we should really do this when the consumer comes
-        # back for an access token. If they never do, we've got stuff in a
-        # weird state.
-
-        $nickname = $params['omb_listenee_nickname'];
-        $fullname = $params['omb_listenee_fullname'];
-        $profile_url = $params['omb_listenee_profile'];
-        $homepage = $params['omb_listenee_homepage'];
-        $bio = $params['omb_listenee_bio'];
-        $location = $params['omb_listenee_location'];
-        $avatar_url = $params['omb_listenee_avatar'];
-
-        $listenee = $params['omb_listenee'];
-        $remote = Remote_profile::staticGet('uri', $listenee);
-
-        if ($remote) {
-            $exists = true;
-            $profile = Profile::staticGet($remote->id);
-            $orig_remote = clone($remote);
-            $orig_profile = clone($profile);
-        } else {
-            $exists = false;
-            $remote = new Remote_profile();
-            $remote->uri = $listenee;
-            $profile = new Profile();
-        }
-
-        $profile->nickname = $nickname;
-        $profile->profileurl = $profile_url;
-
-        if (!is_null($fullname)) {
-            $profile->fullname = $fullname;
-        }
-        if (!is_null($homepage)) {
-            $profile->homepage = $homepage;
-        }
-        if (!is_null($bio)) {
-            $profile->bio = $bio;
-        }
-        if (!is_null($location)) {
-            $profile->location = $location;
-        }
-
-        if ($exists) {
-            $profile->update($orig_profile);
-        } else {
-            $profile->created = DB_DataObject_Cast::dateTime(); # current time
-            $id = $profile->insert();
-            if (!$id) {
-                return false;
-            }
-            $remote->id = $id;
+        $accepted = $this->arg('accept');
+        try {
+            list($val, $token) = $srv->continueUserAuth($accepted);
+        } catch (Exception $e) {
+            $this->clientError($e->getMessage());
+            return;
         }
-
-        if ($exists) {
-            if (!$remote->update($orig_remote)) {
-                return false;
-            }
+        if ($val !== false) {
+            common_redirect($val, 303);
+        } elseif ($accepted) {
+            $this->showAcceptMessage($token);
         } else {
-            $remote->created = DB_DataObject_Cast::dateTime(); # current time
-            if (!$remote->insert()) {
-                return false;
-            }
-        }
-
-        if ($avatar_url) {
-            if (!$this->addAvatar($profile, $avatar_url)) {
-                return false;
-            }
-        }
-
-        $user = common_current_user();
-
-        $sub = new Subscription();
-        $sub->subscriber = $user->id;
-        $sub->subscribed = $remote->id;
-        $sub->token = $params['oauth_token']; # NOTE: request token, not valid for use!
-        $sub->created = DB_DataObject_Cast::dateTime(); # current time
-
-        if (!$sub->insert()) {
-            return false;
+            $this->showRejectMessage();
         }
-
-        return true;
-    }
-
-    function addAvatar($profile, $url)
-    {
-        $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
-        copy($url, $temp_filename);
-        $imagefile = new ImageFile($profile->id, $temp_filename);
-        $filename = Avatar::filename($profile->id,
-                                     image_type_to_extension($imagefile->type),
-                                     null,
-                                     common_timestamp());
-        rename($temp_filename, Avatar::path($filename));
-        return $profile->setOriginal($filename);
     }
 
     function showAcceptMessage($tok)
@@ -378,26 +249,28 @@ class UserauthorizationAction extends Action
         common_show_header(_('Subscription authorized'));
         $this->element('p', null,
                        _('The subscription has been authorized, but no '.
-                         'callback URL was passed. Check with the site\'s instructions for '.
-                         'details on how to authorize the subscription. Your subscription token is:'));
+                         'callback URL was passed. Check with the site\'s ' .
+                         'instructions for details on how to authorize the ' .
+                         'subscription. Your subscription token is:'));
         $this->element('blockquote', 'token', $tok);
         common_show_footer();
     }
 
-    function showRejectMessage($tok)
+    function showRejectMessage()
     {
         common_show_header(_('Subscription rejected'));
         $this->element('p', null,
                        _('The subscription has been rejected, but no '.
-                         'callback URL was passed. Check with the site\'s instructions for '.
-                         'details on how to fully reject the subscription.'));
+                         'callback URL was passed. Check with the site\'s ' .
+                         'instructions for details on how to fully reject ' .
+                         'the subscription.'));
         common_show_footer();
     }
 
     function storeParams($params)
     {
         common_ensure_session();
-        $_SESSION['userauthorizationparams'] = $params;
+        $_SESSION['userauthorizationparams'] = serialize($params);
     }
 
     function clearParams()
@@ -409,138 +282,65 @@ class UserauthorizationAction extends Action
     function getStoredParams()
     {
         common_ensure_session();
-        $params = $_SESSION['userauthorizationparams'];
+        $params = unserialize($_SESSION['userauthorizationparams']);
         return $params;
     }
 
-    # Throws an OAuthException if anything goes wrong
-
-    function validateRequest()
-    {
-        /* Find token.
-           TODO: If no token is passed the user should get a prompt to enter it
-                 according to OAuth Core 1.0 */
-        $t = new Token();
-        $t->tok = $_GET['oauth_token'];
-        $t->type = 0;
-        if (!$t->find(true)) {
-            throw new OAuthException("Invalid request token: " . $_GET['oauth_token']);
-        }
-
-        $this->validateOmb();
-        return true;
-    }
-
     function validateOmb()
     {
-        foreach (array('omb_version', 'omb_listener', 'omb_listenee',
-                       'omb_listenee_profile', 'omb_listenee_nickname',
-                       'omb_listenee_license') as $param)
-        {
-            if (!isset($_GET[$param]) || is_null($_GET[$param])) {
-                throw new OAuthException("Required parameter '$param' not found");
-            }
-        }
-        # Now, OMB stuff
-        $version = $_GET['omb_version'];
-        if ($version != OMB_VERSION_01) {
-            throw new OAuthException("OpenMicroBlogging version '$version' not supported");
-        }
         $listener = $_GET['omb_listener'];
+        $listenee = $_GET['omb_listenee'];
+        $nickname = $_GET['omb_listenee_nickname'];
+        $profile  = $_GET['omb_listenee_profile'];
+
         $user = User::staticGet('uri', $listener);
         if (!$user) {
-            throw new OAuthException("Listener URI '$listener' not found here");
+            throw new Exception("Listener URI '$listener' not found here");
         }
         $cur = common_current_user();
         if ($cur->id != $user->id) {
-            throw new OAuthException("Can't add for another user!");
-        }
-        $listenee = $_GET['omb_listenee'];
-        if (!Validate::uri($listenee) &&
-            !common_valid_tag($listenee)) {
-            throw new OAuthException("Listenee URI '$listenee' not a recognizable URI");
-        }
-        if (strlen($listenee) > 255) {
-            throw new OAuthException("Listenee URI '$listenee' too long");
+            throw new Exception('Can\'t subscribe for another user!');
         }
 
         $other = User::staticGet('uri', $listenee);
         if ($other) {
-            throw new OAuthException("Listenee URI '$listenee' is local user");
+            throw new Exception("Listenee URI '$listenee' is local user");
         }
 
         $remote = Remote_profile::staticGet('uri', $listenee);
         if ($remote) {
-            $sub = new Subscription();
+            $sub             = new Subscription();
             $sub->subscriber = $user->id;
             $sub->subscribed = $remote->id;
             if ($sub->find(true)) {
-                throw new OAuthException("Already subscribed to user!");
+                throw new Exception('You are already subscribed to this user.');
             }
         }
-        $nickname = $_GET['omb_listenee_nickname'];
-        if (!Validate::string($nickname, array('min_length' => 1,
-                                               'max_length' => 64,
-                                               'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
-            throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
-        }
-        $profile = $_GET['omb_listenee_profile'];
-        if (!common_valid_http_url($profile)) {
-            throw new OAuthException("Invalid profile URL '$profile'.");
-        }
 
-        if ($profile == common_local_url('showstream', array('nickname' => $nickname))) {
-            throw new OAuthException("Profile URL '$profile' is for a local user.");
+        if ($profile == common_profile_url($nickname)) {
+            throw new Exception("Profile URL '$profile' is for a local user.");
         }
 
-        $license = $_GET['omb_listenee_license'];
-        if (!common_valid_http_url($license)) {
-            throw new OAuthException("Invalid license URL '$license'.");
-        }
+        $license      = $_GET['omb_listenee_license'];
         $site_license = common_config('license', 'url');
         if (!common_compatible_license($license, $site_license)) {
-            throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'.");
-        }
-        # optional stuff
-        $fullname = $_GET['omb_listenee_fullname'];
-        if ($fullname && mb_strlen($fullname) > 255) {
-            throw new OAuthException("Full name '$fullname' too long.");
-        }
-        $homepage = $_GET['omb_listenee_homepage'];
-        if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
-            throw new OAuthException("Invalid homepage '$homepage'");
-        }
-        $bio = $_GET['omb_listenee_bio'];
-        if ($bio && mb_strlen($bio) > 140) {
-            throw new OAuthException("Bio too long '$bio'");
-        }
-        $location = $_GET['omb_listenee_location'];
-        if ($location && mb_strlen($location) > 255) {
-            throw new OAuthException("Location too long '$location'");
+            throw new Exception("Listenee stream license '$license' is not " .
+                                "compatible with site license '$site_license'.");
         }
         $avatar = $_GET['omb_listenee_avatar'];
         if ($avatar) {
             if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
-                throw new OAuthException("Invalid avatar URL '$avatar'");
+                throw new Exception("Invalid avatar URL '$avatar'");
             }
             $size = @getimagesize($avatar);
             if (!$size) {
-                throw new OAuthException("Can't read avatar URL '$avatar'");
-            }
-            if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
-                throw new OAuthException("Wrong size image at '$avatar'");
+                throw new Exception("Can't read avatar URL '$avatar'.");
             }
             if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
                                           IMAGETYPE_PNG))) {
-                throw new OAuthException("Wrong image type for '$avatar'");
+                throw new Exception("Wrong image type for '$avatar'");
             }
         }
-        $callback = $_GET['oauth_callback'];
-        if ($callback && !common_valid_http_url($callback)) {
-            throw new OAuthException("Invalid callback URL '$callback'");
-        }
-        if ($callback && $callback == common_local_url('finishremotesubscribe')) {
-            throw new OAuthException("Callback URL '$callback' is for local site.");
-        }
     }
 }
+?>
index 9327a3c83145ecbdf1bc354f790f5e7597e8efdc..7518a5f709609e9825d48e97307ed897cc3c8de1 100644 (file)
@@ -34,6 +34,8 @@ if (!defined('LACONICA')) {
 }
 
 require_once INSTALLDIR.'/lib/omb.php';
+require_once INSTALLDIR.'/extlib/libomb/service_provider.php';
+require_once INSTALLDIR.'/extlib/libomb/xrds_mapper.php';
 
 /**
  * XRDS for OpenID
@@ -52,7 +54,7 @@ class XrdsAction extends Action
      *
      * @return boolean true
      */
-    function isReadOnly($args)
+    function isReadOnly()
     {
         return true;
     }
@@ -85,89 +87,31 @@ class XrdsAction extends Action
      */
     function showXrds($user)
     {
-        header('Content-Type: application/xrds+xml');
-        $this->startXML();
-        $this->elementStart('XRDS', array('xmlns' => 'xri://$xrds'));
+        $srv = new OMB_Service_Provider(profile_to_omb_profile($user->uri,
+                                        $user->getProfile()));
+        /* Use libomb’s default XRDS Writer. */
+        $xrds_writer = null;
+        $srv->writeXRDS(new Laconica_XRDS_Mapper(), $xrds_writer);
+    }
+}
 
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'xml:id' => 'oauth',
-                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        $this->showService(OAUTH_ENDPOINT_REQUEST,
-                            common_local_url('requesttoken'),
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1),
-                            $user->uri);
-        $this->showService(OAUTH_ENDPOINT_AUTHORIZE,
-                            common_local_url('userauthorization'),
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1));
-        $this->showService(OAUTH_ENDPOINT_ACCESS,
-                            common_local_url('accesstoken'),
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1));
-        $this->showService(OAUTH_ENDPOINT_RESOURCE,
-                            null,
-                            array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
-                            array(OAUTH_HMAC_SHA1));
-        $this->elementEnd('XRD');
+class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper
+{
+    protected $urls;
 
-        // XXX: decide whether to include user's ID/nickname in postNotice URL
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'xml:id' => 'omb',
-                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        $this->showService(OMB_ENDPOINT_POSTNOTICE,
-                            common_local_url('postnotice'));
-        $this->showService(OMB_ENDPOINT_UPDATEPROFILE,
-                            common_local_url('updateprofile'));
-        $this->elementEnd('XRD');
-        $this->elementStart('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
-                                          'version' => '2.0'));
-        $this->element('Type', null, 'xri://$xrds*simple');
-        $this->showService(OAUTH_DISCOVERY,
-                            '#oauth');
-        $this->showService(OMB_NAMESPACE,
-                            '#omb');
-        $this->elementEnd('XRD');
-        $this->elementEnd('XRDS');
-        $this->endXML();
+    public function __construct()
+    {
+        $this->urls = array(
+            OAUTH_ENDPOINT_REQUEST => 'requesttoken',
+            OAUTH_ENDPOINT_AUTHORIZE => 'userauthorization',
+            OAUTH_ENDPOINT_ACCESS => 'accesstoken',
+            OMB_ENDPOINT_POSTNOTICE => 'postnotice',
+            OMB_ENDPOINT_UPDATEPROFILE => 'updateprofile');
     }
 
-    /**
-     * Show service.
-     *
-     * @param string $type    XRDS type
-     * @param string $uri     URI
-     * @param array  $params  type parameters, null by default
-     * @param array  $sigs    type signatures, null by default
-     * @param string $localId local ID, null by default
-     *
-     * @return void
-     */
-    function showService($type, $uri, $params=null, $sigs=null, $localId=null)
+    public function getURL($action)
     {
-        $this->elementStart('Service');
-        if ($uri) {
-            $this->element('URI', null, $uri);
-        }
-        $this->element('Type', null, $type);
-        if ($params) {
-            foreach ($params as $param) {
-                $this->element('Type', null, $param);
-            }
-        }
-        if ($sigs) {
-            foreach ($sigs as $sig) {
-                $this->element('Type', null, $sig);
-            }
-        }
-        if ($localId) {
-            $this->element('LocalID', null, $localId);
-        }
-        $this->elementEnd('Service');
+        return common_local_url($this->urls[$action]);
     }
 }
-
+?>
diff --git a/extlib/libomb/base_url_xrds_mapper.php b/extlib/libomb/base_url_xrds_mapper.php
new file mode 100755 (executable)
index 0000000..6454595
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+require_once 'xrds_mapper.php';
+require_once 'constants.php';
+
+/**
+ * Map XRDS actions to URLs using base URLs.
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Base_URL_XRDS_Mapper implements OMB_XRDS_Mapper {
+
+  protected $urls;
+
+  public function __construct($oauth_base, $omb_base) {
+    $this->urls = array(
+        OAUTH_ENDPOINT_REQUEST => $oauth_base . 'requesttoken',
+        OAUTH_ENDPOINT_AUTHORIZE => $oauth_base . 'userauthorization',
+        OAUTH_ENDPOINT_ACCESS => $oauth_base . 'accesstoken',
+        OMB_ENDPOINT_POSTNOTICE => $omb_base . 'postnotice',
+        OMB_ENDPOINT_UPDATEPROFILE => $omb_base . 'updateprofile');
+  }
+
+  public function getURL($action) {
+    return $this->urls[$action];
+  }
+}
+?>
diff --git a/extlib/libomb/constants.php b/extlib/libomb/constants.php
new file mode 100644 (file)
index 0000000..a097443
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Constants for libomb
+ *
+ * This file contains constant definitions for libomb. The defined constants
+ * are service and namespace URIs for OAuth and OMB as used in XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+/**
+ * The OMB constants.
+ **/
+
+define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
+
+/* The OMB version supported by this libomb version. */
+define('OMB_VERSION', OMB_VERSION_01);
+
+define('OMB_ENDPOINT_UPDATEPROFILE', OMB_VERSION . '/updateProfile');
+define('OMB_ENDPOINT_POSTNOTICE', OMB_VERSION . '/postNotice');
+
+/**
+ * The OAuth constants.
+ **/
+
+define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
+
+define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
+define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
+define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
+define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
+
+define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
+define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
+
+define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
+
+define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
+?>
diff --git a/extlib/libomb/datastore.php b/extlib/libomb/datastore.php
new file mode 100755 (executable)
index 0000000..ac51a4a
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+
+require_once 'OAuth.php';
+
+/**
+ * Data access interface
+ *
+ * This interface specifies data access methods libomb needs. It
+ * should be implemented by libomb users.
+ * OMB_Datastore is libomb’s main interface to the application’s data.
+ *
+ * It is the user’s duty to signal and handle errors. libomb does not check
+ * return values nor handle exceptions. It is suggested to use exceptions.
+ * Note that lookup_token and getProfile return null if the requested object
+ * is not available. This is NOT an error and should not raise an exception.
+ * Same applies for lookup_nonce which returns a boolean value. These methods
+ * may nevertheless throw an exception, for example in case of a storage error.
+ *
+ * Objects corresponding to this interface are used in OMB_Service_Provider and
+ * OMB_Service_Consumer.
+ *
+ * OMB_Datastore extends OAuthDataStore with two OAuth-related methods for token
+ * revoking and authorizing and all OMB-related methods.
+ * Refer to OAuth.php for a complete specification of OAuth-related methods.
+ *
+ * Note that it’s implemented as a class since OAuthDataStore is as well a
+ * class, though only declaring methods.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Datastore extends OAuthDataStore {
+
+  /*********
+   * OAUTH *
+   *********/
+
+  /**
+   * Revoke specified OAuth token
+   *
+   * Revokes the authorization token specified by $token_key.
+   * Throws exceptions in case of error.
+   *
+   * @param string $token_key The token to be revoked
+   *
+   * @access public
+   **/
+  public function revoke_token($token_key) {
+    throw new Exception();
+  }
+
+  /**
+   * Authorize specified OAuth token
+   *
+   * Authorizes the authorization token specified by $token_key.
+   * Throws exceptions in case of error.
+   *
+   * @param string $token_key The token to be authorized
+   *
+   * @access public
+   **/
+  public function authorize_token($token_key) {
+    throw new Exception();
+  }
+
+  /*********
+   *  OMB  *
+   *********/
+
+  /**
+   * Get profile by identifying URI
+   *
+   * Returns an OMB_Profile object representing the OMB profile identified by
+   * $identifier_uri.
+   * Returns null if there is no such OMB profile.
+   * Throws exceptions in case of other error.
+   *
+   * @param string $identifier_uri The OMB identifier URI specifying the
+   *                               requested profile
+   *
+   * @access public
+   *
+   * @return OMB_Profile The corresponding profile
+   **/
+  public function getProfile($identifier_uri) {
+    throw new Exception();
+  }
+
+  /**
+   * Save passed profile
+   *
+   * Stores the OMB profile $profile. Overwrites an existing entry.
+   * Throws exceptions in case of error.
+   *
+   * @param OMB_Profile $profile   The OMB profile which should be saved
+   *
+   * @access public
+   **/
+  public function saveProfile($profile) {
+    throw new Exception();
+  }
+
+  /**
+   * Save passed notice
+   *
+   * Stores the OMB notice $notice. The datastore may change the passed notice.
+   * This might by neccessary for URIs depending on a database key. Note that
+   * it is the user’s duty to present a mechanism for his OMB_Datastore to
+   * appropriately change his OMB_Notice. TODO: Ugly.
+   * Throws exceptions in case of error.
+   *
+   * @param OMB_Notice $notice The OMB notice which should be saved
+   *
+   * @access public
+   **/
+  public function saveNotice(&$notice) {
+    throw new Exception();
+  }
+
+  /**
+   * Get subscriptions of a given profile
+   *
+   * Returns an array containing subscription informations for the specified
+   * profile. Every array entry should in turn be an array with keys
+   *   'uri´: The identifier URI of the subscriber
+   *   'token´: The subscribe token
+   *   'secret´: The secret token
+   * Throws exceptions in case of error.
+   *
+   * @param string $subscribed_user_uri The OMB identifier URI specifying the
+   *                                    subscribed profile
+   *
+   * @access public
+   *
+   * @return mixed An array containing the subscriptions or 0 if no
+   *               subscription has been found.
+   **/
+  public function getSubscriptions($subscribed_user_uri) {
+    throw new Exception();
+  }
+
+  /**
+   * Delete a subscription
+   *
+   * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
+   * Throws exceptions in case of error.
+   *
+   * @param string $subscriber_uri      The OMB identifier URI specifying the
+   *                                    subscribing profile
+   *
+   * @param string $subscribed_user_uri The OMB identifier URI specifying the
+   *                                    subscribed profile
+   *
+   * @access public
+   **/
+  public function deleteSubscription($subscriber_uri, $subscribed_user_uri) {
+    throw new Exception();
+  }
+
+  /**
+   * Save a subscription
+   *
+   * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
+   * Throws exceptions in case of error.
+   *
+   * @param string     $subscriber_uri      The OMB identifier URI specifying
+   *                                        the subscribing profile
+   *
+   * @param string     $subscribed_user_uri The OMB identifier URI specifying
+   *                                        the subscribed profile
+   * @param OAuthToken $token               The access token
+   *
+   * @access public
+   **/
+  public function saveSubscription($subscriber_uri, $subscribed_user_uri,
+                                                                       $token) {
+    throw new Exception();
+  }
+}
+?>
diff --git a/extlib/libomb/helper.php b/extlib/libomb/helper.php
new file mode 100644 (file)
index 0000000..a1f21f2
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+
+require_once 'Validate.php';
+
+/**
+ * Helper functions for libomb
+ *
+ * This file contains helper functions for libomb.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Helper {
+
+  /**
+   * Non-scalar constants
+   *
+   * The set of OMB and OAuth Services an OMB Server has to implement.
+   */
+
+  public static $OMB_SERVICES =
+    array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
+  public static $OAUTH_SERVICES =
+    array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE, OAUTH_ENDPOINT_ACCESS);
+
+  /**
+   * Validate URL
+   *
+   * Basic URL validation. Currently http, https, ftp and gopher are supported
+   * schemes.
+   *
+   * @param string $url The URL which is to be validated.
+   *
+   * @return bool Whether URL is valid.
+   *
+   * @access public
+   */
+  public static function validateURL($url) {
+    return Validate::uri($url, array('allowed_schemes' => array('http', 'https',
+            'gopher', 'ftp')));
+  }
+
+  /**
+   * Validate Media type
+   *
+   * Basic Media type validation. Checks for valid maintype and correct format.
+   *
+   * @param string $mediatype The Media type which is to be validated.
+   *
+   * @return bool Whether media type is valid.
+   *
+   * @access public
+   */
+  public static function validateMediaType($mediatype) {
+    if (0 === preg_match('/^(\w+)\/([\w\d-+.]+)$/', $mediatype, $subtypes)) {
+      return false;
+    }
+    if (!in_array(strtolower($subtypes[1]), array('application', 'audio', 'image',
+              'message', 'model', 'multipart', 'text', 'video'))) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Remove escaping from request parameters
+   *
+   * Neutralise the evil effects of magic_quotes_gpc in the current request.
+   * This is used before handing a request off to OAuthRequest::from_request.
+   * Many thanks to Ciaran Gultnieks for this fix.
+   *
+   * @access public
+   */
+  public static function removeMagicQuotesFromRequest() {
+    if(get_magic_quotes_gpc() == 1) {
+      $_POST = array_map('stripslashes', $_POST);
+      $_GET = array_map('stripslashes', $_GET);
+    }
+  }
+}
+?>
diff --git a/extlib/libomb/invalidparameterexception.php b/extlib/libomb/invalidparameterexception.php
new file mode 100755 (executable)
index 0000000..163e1dd
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Exception stating that a passed parameter is invalid
+ *
+ * This exception is raised when a parameter does not obey the OMB standard.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_InvalidParameterException extends Exception {
+  public function __construct($value, $type, $parameter) {
+    parent::__construct("Invalid value $value for parameter $parameter in $type");
+  }
+}
+?>
diff --git a/extlib/libomb/invalidyadisexception.php b/extlib/libomb/invalidyadisexception.php
new file mode 100755 (executable)
index 0000000..797b7b9
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Exception stating that a requested url does not resolve to a valid yadis
+ *
+ * This exception is raised when OMB_Service is not able to discover a valid
+ * yadis location with XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_InvalidYadisException extends Exception {
+
+}
+?>
diff --git a/extlib/libomb/notice.php b/extlib/libomb/notice.php
new file mode 100755 (executable)
index 0000000..9ac3664
--- /dev/null
@@ -0,0 +1,272 @@
+<?php
+require_once 'invalidparameterexception.php';
+require_once 'Validate.php';
+require_once 'helper.php';
+
+/**
+ * OMB Notice representation
+ *
+ * This class represents an OMB notice.
+ *
+ * Do not call the setters with null values. Instead, if you want to delete a
+ * field, pass an empty string. The getters will return null for empty fields.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Notice {
+  protected $author;
+  protected $uri;
+  protected $content;
+  protected $url;
+  protected $license_url; /* url is an own addition for clarification. */
+  protected $seealso_url; /* url is an own addition for clarification. */
+  protected $seealso_disposition;
+  protected $seealso_mediatype;
+  protected $seealso_license_url; /* url is an addition for clarification. */
+
+  /* The notice as OMB param array. Cached and rebuild on usage.
+     false while outdated. */
+  protected $param_array;
+
+  /**
+   * Constructor for OMB_Notice
+   *
+   * Initializes the OMB_Notice object with author, uri and content.
+   * These parameters are mandatory for postNotice.
+   *
+   * @param object $author  An OMB_Profile object representing the author of the
+   *                        notice.
+   * @param string $uri     The notice URI as defined by the OMB. A unique and
+   *                        unchanging identifier for a notice.
+   * @param string $content The content of the notice. 140 chars recommended,
+   *                        but there is no limit.
+   *
+   * @access public
+   */
+  public function __construct($author, $uri, $content) {
+    $this->content = $content;
+    if (is_null($author)) {
+      throw new OMB_InvalidParameterException('', 'notice', 'omb_listenee');
+    }
+    $this->author = $author;
+
+    if (!Validate::uri($uri)) {
+      throw new OMB_InvalidParameterException($uri, 'notice', 'omb_notice');
+    }
+    $this->uri = $uri;
+
+    $this->param_array = false;
+  }
+
+  /**
+   * Returns the notice as array
+   *
+   * The method returns an array which contains the whole notice as array. The
+   * array is cached and only rebuilt on changes of the notice.
+   * Empty optional values are not passed.
+   *
+   *  @access  public
+   *  @returns array The notice as parameter array
+   */
+  public function asParameters() {
+    if ($this->param_array !== false) {
+      return $this->param_array;
+    }
+
+    $this->param_array = array(
+                 'omb_notice' => $this->uri,
+                 'omb_notice_content' => $this->content);
+
+    if (!is_null($this->url))
+      $this->param_array['omb_notice_url'] = $this->url;
+
+    if (!is_null($this->license_url))
+      $this->param_array['omb_notice_license'] = $this->license_url;
+
+    if (!is_null($this->seealso_url)) {
+      $this->param_array['omb_seealso'] = $this->seealso_url;
+
+      /* This is actually a free interpretation of the OMB standard. We assume
+         that additional seealso parameters are not of any use if seealso itself
+         is not set. */
+      if (!is_null($this->seealso_disposition))
+        $this->param_array['omb_seealso_disposition'] =
+                                                     $this->seealso_disposition;
+
+      if (!is_null($this->seealso_mediatype))
+        $this->param_array['omb_seealso_mediatype'] = $this->seealso_mediatype;
+
+      if (!is_null($this->seealso_license_url))
+        $this->param_array['omb_seealso_license'] = $this->seealso_license_url;
+    }
+    return $this->param_array;
+  }
+
+  /**
+   * Builds an OMB_Notice object from array
+   *
+   * The method builds an OMB_Notice object from the passed parameters array.
+   * The array MUST provide a notice URI and content. The array fields HAVE TO
+   * be named according to the OMB standard, i. e. omb_notice_* and
+   * omb_seealso_*. Values are handled as not passed if the corresponding array
+   * fields are not set or the empty string.
+   *
+   * @param object $author     An OMB_Profile object representing the author of
+   *                           the notice.
+   * @param string $parameters An array containing the notice parameters.
+   *
+   * @access public
+   *
+   * @returns OMB_Notice The built OMB_Notice.
+   */
+  public static function fromParameters($author, $parameters) {
+    $notice = new OMB_Notice($author, $parameters['omb_notice'],
+                             $parameters['omb_notice_content']);
+
+    if (isset($parameters['omb_notice_url'])) {
+      $notice->setURL($parameters['omb_notice_url']);
+    }
+
+    if (isset($parameters['omb_notice_license'])) {
+      $notice->setLicenseURL($parameters['omb_notice_license']);
+    }
+
+    if (isset($parameters['omb_seealso'])) {
+      $notice->setSeealsoURL($parameters['omb_seealso']);
+    }
+
+    if (isset($parameters['omb_seealso_disposition'])) {
+      $notice->setSeealsoDisposition($parameters['omb_seealso_disposition']);
+    }
+
+    if (isset($parameters['omb_seealso_mediatype'])) {
+      $notice->setSeealsoMediatype($parameters['omb_seealso_mediatype']);
+    }
+
+    if (isset($parameters['omb_seealso_license'])) {
+      $notice->setSeealsoLicenseURL($parameters['omb_seealso_license']);
+    }
+    return $notice;
+  }
+
+  public function getAuthor() {
+    return $this->author;
+  }
+
+  public function getIdentifierURI() {
+    return $this->uri;
+  }
+
+  public function getContent() {
+    return $this->content;
+  }
+
+  public function getURL() {
+    return $this->url;
+  }
+
+  public function getLicenseURL() {
+    return $this->license_url;
+  }
+
+  public function getSeealsoURL() {
+    return $this->seealso_url;
+  }
+
+  public function getSeealsoDisposition() {
+    return $this->seealso_disposition;
+  }
+
+  public function getSeealsoMediatype() {
+    return $this->seealso_mediatype;
+  }
+
+  public function getSeealsoLicenseURL() {
+    return $this->seealso_license_url;
+  }
+
+  public function setURL($url) {
+    if ($url === '') {
+      $url = null;
+    } elseif (!OMB_Helper::validateURL($url)) {
+      throw new OMB_InvalidParameterException($url, 'notice', 'omb_notice_url');
+    }
+    $this->url = $url;
+    $this->param_array = false;
+  }
+
+  public function setLicenseURL($license_url) {
+    if ($license_url === '') {
+      $license_url = null;
+    } elseif (!OMB_Helper::validateURL($license_url)) {
+      throw new OMB_InvalidParameterException($license_url, 'notice',
+                                              'omb_notice_license');
+    }
+    $this->license_url = $license_url;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoURL($seealso_url) {
+    if ($seealso_url === '') {
+      $seealso_url = null;
+    } elseif (!OMB_Helper::validateURL($seealso_url)) {
+      throw new OMB_InvalidParameterException($seealso_url, 'notice',
+                                              'omb_seealso');
+    }
+    $this->seealso_url = $seealso_url;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoDisposition($seealso_disposition) {
+    if ($seealso_disposition === '') {
+      $seealso_disposition = null;
+    } elseif ($seealso_disposition !== 'link' && $seealso_disposition !== 'inline') {
+      throw new OMB_InvalidParameterException($seealso_disposition, 'notice',
+                                              'omb_seealso_disposition');
+    }
+    $this->seealso_disposition = $seealso_disposition;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoMediatype($seealso_mediatype) {
+    if ($seealso_mediatype === '') {
+      $seealso_mediatype = null;
+    } elseif (!OMB_Helper::validateMediaType($seealso_mediatype)) {
+      throw new OMB_InvalidParameterException($seealso_mediatype, 'notice',
+                                              'omb_seealso_mediatype');
+    }
+    $this->seealso_mediatype = $seealso_mediatype;
+    $this->param_array = false;
+  }
+
+  public function setSeealsoLicenseURL($seealso_license_url) {
+    if ($seealso_license_url === '') {
+      $seealso_license_url = null;
+    } elseif (!OMB_Helper::validateURL($seealso_license_url)) {
+      throw new OMB_InvalidParameterException($seealso_license_url, 'notice',
+                                              'omb_seealso_license');
+    }
+    $this->seealso_license_url = $seealso_license_url;
+    $this->param_array = false;
+  }
+}
+?>
diff --git a/extlib/libomb/omb_yadis_xrds.php b/extlib/libomb/omb_yadis_xrds.php
new file mode 100755 (executable)
index 0000000..8992120
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'unsupportedserviceexception.php';
+require_once 'invalidyadisexception.php';
+
+/**
+ * OMB XRDS representation
+ *
+ * This class represents a Yadis XRDS file for OMB. It adds some useful methods to
+ * Auth_Yadis_XRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Yadis_XRDS extends Auth_Yadis_XRDS {
+
+  protected $fetcher;
+
+  /**
+   * Create an instance from URL
+   *
+   * Constructs an OMB_Yadis_XRDS object from a given URL. A full Yadis
+   * discovery is performed on the URL and the XRDS is parsed.
+   * Throws an OMB_InvalidYadisException when no Yadis is discovered or the
+   * detected XRDS file is broken.
+   *
+   * @param string                 $url     The URL on which Yadis discovery
+   *                                        should be performed on
+   * @param Auth_Yadis_HTTPFetcher $fetcher A fetcher used to get HTTP
+   *                                        resources
+   *
+   * @access public
+   *
+   * @return OMB_Yadis_XRDS The initialized object representing the given
+   *                        resource
+   **/
+  public static function fromYadisURL($url, $fetcher) {
+    /* Perform a Yadis discovery. */
+    $yadis = Auth_Yadis_Yadis::discover($url, $fetcher);
+    if ($yadis->failed) {
+      throw new OMB_InvalidYadisException($url);
+    }
+
+    /* Parse the XRDS file. */
+    $xrds = OMB_Yadis_XRDS::parseXRDS($yadis->response_text);
+    if ($xrds === null) {
+      throw new OMB_InvalidYadisException($url);
+    }
+    $xrds->fetcher = $fetcher;
+    return $xrds;
+  }
+
+  /**
+   * Get a specific service
+   *
+   * Returns the Auth_Yadis_Service object corresponding to the given service
+   * URI.
+   * Throws an OMB_UnsupportedServiceException if the service is not available.
+   *
+   * @param string $service URI specifier of the requested service
+   *
+   * @access public
+   *
+   * @return Auth_Yadis_Service The object representing the requested service
+   **/
+  public function getService($service) {
+    $match = $this->services(array( create_function('$s',
+                           "return in_array('$service', \$s->getTypes());")));
+    if ($match === array()) {
+      throw new OMB_UnsupportedServiceException($service);
+    }
+    return $match[0];
+  }
+
+  /**
+   * Get a specific XRD
+   *
+   * Returns the OMB_Yadis_XRDS object corresponding to the given URI.
+   * Throws an OMB_UnsupportedServiceException if the XRD is not available.
+   * Note that getXRD tries to resolve external XRD parts as well.
+   *
+   * @param string $uri URI specifier of the requested XRD
+   *
+   * @access public
+   *
+   * @return OMB_Yadis_XRDS The object representing the requested XRD
+   **/
+  public function getXRD($uri) {
+    $nexthash = strpos($uri, '#');
+    if ($nexthash !== 0) {
+      if ($nexthash !== false) {
+        $cururi = substr($uri, 0, $nexthash);
+        $nexturi = substr($uri, $nexthash);
+      }
+      return
+        OMB_Yadis_XRDS::fromYadisURL($cururi, $this->fetcher)->getXRD($nexturi);
+    }
+
+    $id = substr($uri, 1);
+    foreach ($this->allXrdNodes as $node) {
+      $attrs = $this->parser->attributes($node);
+      if (array_key_exists('xml:id', $attrs) && $attrs['xml:id'] == $id) {
+        /* Trick the constructor into thinking this is the only node. */
+        $bogus_nodes = array($node);
+        return new OMB_Yadis_XRDS($this->parser, $bogus_nodes);
+      }
+    }
+    throw new OMB_UnsupportedServiceException($uri);
+  }
+
+  /**
+   * Parse an XML string containing a XRDS document
+   *
+   * Parse an XML string (XRDS document) and return either a
+   * Auth_Yadis_XRDS object or null, depending on whether the
+   * XRDS XML is valid.
+   * Copy and paste from parent to select correct constructor.
+   *
+   * @param string $xml_string An XRDS XML string.
+   *
+   * @access public
+   *
+   * @return mixed An instance of OMB_Yadis_XRDS or null,
+   *               depending on the validity of $xml_string
+   **/
+
+  public function &parseXRDS($xml_string, $extra_ns_map = null) {
+    $_null = null;
+
+    if (!$xml_string) {
+      return $_null;
+    }
+
+    $parser = Auth_Yadis_getXMLParser();
+
+    $ns_map = Auth_Yadis_getNSMap();
+
+    if ($extra_ns_map && is_array($extra_ns_map)) {
+      $ns_map = array_merge($ns_map, $extra_ns_map);
+    }
+
+    if (!($parser && $parser->init($xml_string, $ns_map))) {
+      return $_null;
+    }
+
+    // Try to get root element.
+    $root = $parser->evalXPath('/xrds:XRDS[1]');
+    if (!$root) {
+      return $_null;
+    }
+
+    if (is_array($root)) {
+      $root = $root[0];
+    }
+
+    $attrs = $parser->attributes($root);
+
+    if (array_key_exists('xmlns:xrd', $attrs) &&
+          $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
+      return $_null;
+    } else if (array_key_exists('xmlns', $attrs) &&
+                   preg_match('/xri/', $attrs['xmlns']) &&
+                   $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+      return $_null;
+    }
+
+    // Get the last XRD node.
+    $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+    if (!$xrd_nodes) {
+      return $_null;
+    }
+
+    $xrds = new OMB_Yadis_XRDS($parser, $xrd_nodes);
+    return $xrds;
+  }
+}
diff --git a/extlib/libomb/plain_xrds_writer.php b/extlib/libomb/plain_xrds_writer.php
new file mode 100755 (executable)
index 0000000..b4a6e99
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+require_once 'xrds_writer.php';
+
+/**
+ * Write OMB-specific XRDS using XMLWriter.
+ *
+ * This class writes the XRDS file announcing the OMB server. It uses
+ * OMB_XMLWriter, which is a subclass of XMLWriter. An instance of
+ * OMB_Plain_XRDS_Writer should be passed to OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Plain_XRDS_Writer implements OMB_XRDS_Writer {
+  public function writeXRDS($user, $mapper) {
+    header('Content-Type: application/xrds+xml');
+    $xw = new XMLWriter();
+    $xw->openURI('php://output');
+    $xw->setIndent(true);
+
+    $xw->startDocument('1.0', 'UTF-8');
+    $this->writeFullElement($xw, 'XRDS',  array('xmlns' => 'xri://$xrds'), array(
+        array('XRD',  array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'oauth',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'), array(
+          array('Type', null, 'xri://$xrds*simple'),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_REQUEST),
+            array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_REQUEST)),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1),
+            array('LocalID', null, $user->getIdentifierURI())
+          )),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_AUTHORIZE),
+            array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_AUTHORIZE)),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1)
+          )),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_ACCESS),
+            array('URI', null, $mapper->getURL(OAUTH_ENDPOINT_ACCESS)),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1)
+          )),
+          array('Service', null, array(
+            array('Type', null, OAUTH_ENDPOINT_RESOURCE),
+            array('Type', null, OAUTH_AUTH_HEADER),
+            array('Type', null, OAUTH_POST_BODY),
+            array('Type', null, OAUTH_HMAC_SHA1)
+          ))
+        )),
+        array('XRD',  array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'xml:id' => 'omb',
+                                          'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+                                          'version' => '2.0'), array(
+          array('Type', null, 'xri://$xrds*simple'),
+          array('Service', null, array(
+            array('Type', null, OMB_ENDPOINT_POSTNOTICE),
+            array('URI', null, $mapper->getURL(OMB_ENDPOINT_POSTNOTICE))
+          )),
+          array('Service', null, array(
+            array('Type', null, OMB_ENDPOINT_UPDATEPROFILE),
+            array('URI', null, $mapper->getURL(OMB_ENDPOINT_UPDATEPROFILE))
+          ))
+        )),
+        array('XRD',  array('xmlns' => 'xri://$xrd*($v*2.0)',
+                                          'version' => '2.0'), array(
+          array('Type', null, 'xri://$xrds*simple'),
+          array('Service', null, array(
+            array('Type', null, OAUTH_DISCOVERY),
+            array('URI', null, '#oauth')
+          )),
+          array('Service', null, array(
+            array('Type', null, OMB_VERSION),
+            array('URI', null, '#omb')
+          ))
+        ))
+      ));
+    $xw->endDocument();
+    $xw->flush();
+  }
+
+  public static function writeFullElement($xw, $tag, $attributes, $content) {
+    $xw->startElement($tag);
+    if (!is_null($attributes)) {
+      foreach ($attributes as $name => $value) {
+        $xw->writeAttribute($name, $value);
+      }
+    }
+    if (is_array($content)) {
+      foreach ($content as $values) {
+        OMB_Plain_XRDS_Writer::writeFullElement($xw, $values[0], $values[1], $values[2]);
+      }
+    } else {
+      $xw->text($content);
+    }
+    $xw->fullEndElement();
+  }
+}
+?>
diff --git a/extlib/libomb/profile.php b/extlib/libomb/profile.php
new file mode 100755 (executable)
index 0000000..13314d3
--- /dev/null
@@ -0,0 +1,317 @@
+<?php
+require_once 'invalidparameterexception.php';
+require_once 'Validate.php';
+require_once 'helper.php';
+
+/**
+ * OMB profile representation
+ *
+ * This class represents an OMB profile.
+ *
+ * Do not call the setters with null values. Instead, if you want to delete a
+ * field, pass an empty string. The getters will return null for empty fields.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Profile {
+  protected $identifier_uri;
+  protected $profile_url;
+  protected $nickname;
+  protected $license_url;
+  protected $fullname;
+  protected $homepage;
+  protected $bio;
+  protected $location;
+  protected $avatar_url;
+
+  /* The profile as OMB param array. Cached and rebuild on usage.
+     false while outdated. */
+  protected $param_array;
+
+  /**
+   * Constructor for OMB_Profile
+   *
+   * Initializes the OMB_Profile object with an identifier uri.
+   *
+   * @param string $identifier_uri The profile URI as defined by the OMB. A unique
+   *                               and unchanging identifier for a profile.
+   *
+   * @access public
+   */
+  public function __construct($identifier_uri) {
+    if (!Validate::uri($identifier_uri)) {
+      throw new OMB_InvalidParameterException($identifier_uri, 'profile',
+                                                'omb_listenee or omb_listener');
+    }
+    $this->identifier_uri = $identifier_uri;
+    $this->param_array = false;
+  }
+
+  /**
+   * Returns the profile as array
+   *
+   * The method returns an array which contains the whole profile as array. The
+   * array is cached and only rebuilt on changes of the profile.
+   *
+   * @param bool   $force_all Specifies whether empty fields should be added to
+   *                          the array as well. This is neccessary to clear
+   *                          fields via updateProfile.
+   *
+   * @param string $prefix    The common prefix to the key for all parameters.
+   *
+   * @access public
+   *
+   * @return array The profile as parameter array
+   */
+  public function asParameters($prefix, $force_all = false) {
+    if ($this->param_array === false) {
+      $this->param_array = array('' => $this->identifier_uri);
+
+      if ($force_all || !is_null($this->profile_url)) {
+        $this->param_array['_profile'] = $this->profile_url;
+      }
+
+      if ($force_all || !is_null($this->homepage)) {
+        $this->param_array['_homepage'] = $this->homepage;
+      }
+
+      if ($force_all || !is_null($this->nickname)) {
+        $this->param_array['_nickname'] = $this->nickname;
+      }
+
+      if ($force_all || !is_null($this->license_url)) {
+        $this->param_array['_license'] = $this->license_url;
+      }
+
+      if ($force_all || !is_null($this->fullname)) {
+        $this->param_array['_fullname'] = $this->fullname;
+      }
+
+      if ($force_all || !is_null($this->bio)) {
+        $this->param_array['_bio'] = $this->bio;
+      }
+
+      if ($force_all || !is_null($this->location)) {
+        $this->param_array['_location'] = $this->location;
+      }
+
+      if ($force_all || !is_null($this->avatar_url)) {
+        $this->param_array['_avatar'] = $this->avatar_url;
+      }
+
+    }
+    $ret = array();
+    foreach ($this->param_array as $k => $v) {
+      $ret[$prefix . $k] = $v;
+    }
+    return $ret;
+  }
+
+  /**
+   * Builds an OMB_Profile object from array
+   *
+   * The method builds an OMB_Profile object from the passed parameters array. The
+   * array MUST provide a profile URI. The array fields HAVE TO be named according
+   * to the OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
+   * parameter.
+   *
+   * @param string $parameters An array containing the profile parameters.
+   * @param string $prefix     The common prefix of the profile parameter keys.
+   *
+   * @access public
+   *
+   * @returns OMB_Profile The built OMB_Profile.
+   */
+  public static function fromParameters($parameters, $prefix) {
+    if (!isset($parameters[$prefix])) {
+      throw new OMB_InvalidParameterException('', 'profile', $prefix);
+    }
+
+    $profile = new OMB_Profile($parameters[$prefix]);
+    $profile->updateFromParameters($parameters, $prefix);
+    return $profile;
+  }
+
+  /**
+   * Update from array
+   *
+   * Updates from the passed parameters array. The array does not have to
+   * provide a profile URI. The array fields HAVE TO be named according to the
+   * OMB standard. The prefix (omb_listener or omb_listenee) is passed as a
+   * parameter.
+   *
+   * @param string $parameters An array containing the profile parameters.
+   * @param string $prefix     The common prefix of the profile parameter keys.
+   *
+   * @access public
+   */
+  public function updateFromParameters($parameters, $prefix) {
+    if (isset($parameters[$prefix.'_profile'])) {
+      $this->setProfileURL($parameters[$prefix.'_profile']);
+    }
+
+    if (isset($parameters[$prefix.'_license'])) {
+      $this->setLicenseURL($parameters[$prefix.'_license']);
+    }
+
+    if (isset($parameters[$prefix.'_nickname'])) {
+      $this->setNickname($parameters[$prefix.'_nickname']);
+    }
+
+    if (isset($parameters[$prefix.'_fullname'])) {
+      $this->setFullname($parameters[$prefix.'_fullname']);
+    }
+
+    if (isset($parameters[$prefix.'_homepage'])) {
+      $this->setHomepage($parameters[$prefix.'_homepage']);
+    }
+
+    if (isset($parameters[$prefix.'_bio'])) {
+      $this->setBio($parameters[$prefix.'_bio']);
+    }
+
+    if (isset($parameters[$prefix.'_location'])) {
+      $this->setLocation($parameters[$prefix.'_location']);
+    }
+
+    if (isset($parameters[$prefix.'_avatar'])) {
+      $this->setAvatarURL($parameters[$prefix.'_avatar']);
+    }
+  }
+
+  public function getIdentifierURI() {
+    return $this->identifier_uri;
+  }
+
+  public function getProfileURL() {
+    return $this->profile_url;
+  }
+
+  public function getHomepage() {
+    return $this->homepage;
+  }
+
+  public function getNickname() {
+    return $this->nickname;
+  }
+
+  public function getLicenseURL() {
+    return $this->license_url;
+  }
+
+  public function getFullname() {
+    return $this->fullname;
+  }
+
+  public function getBio() {
+    return $this->bio;
+  }
+
+  public function getLocation() {
+    return $this->location;
+  }
+
+  public function getAvatarURL() {
+    return $this->avatar_url;
+  }
+
+  public function setProfileURL($profile_url) {
+    if (!OMB_Helper::validateURL($profile_url)) {
+      throw new OMB_InvalidParameterException($profile_url, 'profile',
+                                    'omb_listenee_profile or omb_listener_profile');
+    }
+    $this->profile_url = $profile_url;
+    $this->param_array = false;
+  }
+
+  public function setNickname($nickname) {
+    if (!Validate::string($nickname,
+                          array('min_length' => 1,
+                                'max_length' => 64,
+                                'format' => VALIDATE_NUM . VALIDATE_ALPHA))) {
+      throw new OMB_InvalidParameterException($nickname, 'profile', 'nickname');
+    }
+
+    $this->nickname = $nickname;
+    $this->param_array = false;
+  }
+
+  public function setLicenseURL($license_url) {
+    if (!OMB_Helper::validateURL($license_url)) {
+      throw new OMB_InvalidParameterException($license_url, 'profile',
+                                    'omb_listenee_license or omb_listener_license');
+    }
+    $this->license_url = $license_url;
+    $this->param_array = false;
+  }
+
+  public function setFullname($fullname) {
+    if ($fullname === '') {
+      $fullname = null;
+    } elseif (!Validate::string($fullname, array('max_length' => 255))) {
+      throw new OMB_InvalidParameterException($fullname, 'profile', 'fullname');
+    }
+    $this->fullname = $fullname;
+    $this->param_array = false;
+  }
+
+  public function setHomepage($homepage) {
+    if ($homepage === '') {
+      $homepage = null;
+    }
+    $this->homepage = $homepage;
+    $this->param_array = false;
+  }
+
+  public function setBio($bio) {
+    if ($bio === '') {
+      $bio = null;
+    } elseif (!Validate::string($bio, array('max_length' => 140))) {
+      throw new OMB_InvalidParameterException($bio, 'profile', 'fullname');
+    }
+    $this->bio = $bio;
+    $this->param_array = false;
+  }
+
+  public function setLocation($location) {
+    if ($location === '') {
+      $location = null;
+    } elseif (!Validate::string($location, array('max_length' => 255))) {
+      throw new OMB_InvalidParameterException($location, 'profile', 'fullname');
+    }
+    $this->location = $location;
+    $this->param_array = false;
+  }
+
+  public function setAvatarURL($avatar_url) {
+    if ($avatar_url === '') {
+      $avatar_url = null;
+    } elseif (!OMB_Helper::validateURL($avatar_url)) {
+      throw new OMB_InvalidParameterException($avatar_url, 'profile',
+                                      'omb_listenee_avatar or omb_listener_avatar');
+    }
+    $this->avatar_url = $avatar_url;
+    $this->param_array = false;
+  }
+
+}
+?>
diff --git a/extlib/libomb/remoteserviceexception.php b/extlib/libomb/remoteserviceexception.php
new file mode 100755 (executable)
index 0000000..374d159
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Exception stating that the remote service had a failure
+ *
+ * This exception is raised when a remote service failed to return a valid
+ * response to a request or send a valid request.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_RemoteServiceException extends Exception {
+  public static function fromYadis($request_uri, $result) {
+    if ($result->status == 200) {
+        $err = 'Got wrong response ' . $result->body;
+    } else {
+        $err = 'Got error code ' . $result->status . ' with response ' . $result->body;
+    }
+    return new OMB_RemoteServiceException($request_uri . ': ' .  $err);
+  }
+
+  public static function forRequest($action_uri, $failure) {
+    return new OMB_RemoteServiceException("Handler for $action_uri: " .  $failure);
+  }
+}
+?>
diff --git a/extlib/libomb/service_consumer.php b/extlib/libomb/service_consumer.php
new file mode 100755 (executable)
index 0000000..273fd05
--- /dev/null
@@ -0,0 +1,430 @@
+<?php
+
+require_once 'constants.php';
+require_once 'Validate.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'OAuth.php';
+require_once 'unsupportedserviceexception.php';
+require_once 'remoteserviceexception.php';
+require_once 'omb_yadis_xrds.php';
+require_once 'helper.php';
+
+/**
+ * OMB service representation
+ *
+ * This class represents a complete remote OMB service. It provides discovery
+ * and execution of the service’s methods.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Service_Consumer {
+  protected $url; /* The service URL */
+  protected $services; /* An array of strings mapping service URI to
+                          service URL */
+
+  protected $token; /* An OAuthToken */
+
+  protected $listener_uri; /* The URI identifying the listener, i. e. the
+                              remote user. */
+
+  protected $listenee_uri; /* The URI identifying the listenee, i. e. the
+                              local user during an auth request. */
+
+  /**
+   * According to OAuth Core 1.0, an user authorization request is no full-blown
+   * OAuth request. nonce, timestamp, consumer_key and signature are not needed
+   * in this step. See http://laconi.ca/trac/ticket/827 for more informations.
+   *
+   * Since Laconica up to version 0.7.2 performs a full OAuth request check, a
+   * correct request would fail.
+   **/
+  public $performLegacyAuthRequest = true;
+
+  /* Helper stuff we are going to need. */
+  protected $fetcher;
+  protected $oauth_consumer;
+  protected $datastore;
+
+  /**
+   * Constructor for OMB_Service_Consumer
+   *
+   * Initializes an OMB_Service_Consumer object representing the OMB service
+   * specified by $service_url. Performs a complete service discovery using
+   * Yadis.
+   * Throws OMB_UnsupportedServiceException if XRDS file does not specify a
+   * complete OMB service.
+   *
+   * @param string        $service_url  The URL of the service
+   * @param string        $consumer_url An URL representing the consumer
+   * @param OMB_Datastore $datastore    An instance of a class implementing
+   *                                    OMB_Datastore
+   *
+   * @access public
+   **/
+  public function __construct ($service_url, $consumer_url, $datastore) {
+    $this->url = $service_url;
+    $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+    $this->datastore = $datastore;
+    $this->oauth_consumer = new OAuthConsumer($consumer_url, '');
+
+    $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
+
+    /* Detect our services. This performs a validation as well, since
+       getService und getXRD throw exceptions on failure. */
+    $this->services = array();
+
+    foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
+                   OMB_VERSION     => OMB_Helper::$OMB_SERVICES)
+             as $service_root => $targetservices) {
+      $uris = $xrds->getService($service_root)->getURIs();
+      $xrd = $xrds->getXRD($uris[0]);
+      foreach ($targetservices as $targetservice) {
+        $yadis_service = $xrd->getService($targetservice);
+        if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
+            $localid = $yadis_service->getElements('xrd:LocalID');
+            $this->listener_uri = $yadis_service->parser->content($localid[0]);
+        }
+        $uris = $yadis_service->getURIs();
+        $this->services[$targetservice] = $uris[0];
+      }
+    }
+  }
+
+  /**
+   * Get the handler URI for a service
+   *
+   * Returns the URI the remote web service has specified for the given
+   * service.
+   *
+   * @param string $service The URI identifying the service
+   *
+   * @access public
+   *
+   * @return string The service handler URI
+   **/
+  public function getServiceURI($service) {
+    return $this->services[$service];
+  }
+
+  /**
+   * Get the remote user’s URI
+   *
+   * Returns the URI of the remote user, i. e. the listener.
+   *
+   * @access public
+   *
+   * @return string The remote user’s URI
+   **/
+  public function getRemoteUserURI() {
+    return $this->listener_uri;
+  }
+
+  /**
+   * Get the listenee’s URI
+   *
+   * Returns the URI of the user being subscribed to, i. e. the local user.
+   *
+   * @access public
+   *
+   * @return string The local user’s URI
+   **/
+  public function getListeneeURI() {
+    return $this->listenee_uri;
+  }
+
+  /**
+   * Request a request token
+   *
+   * Performs a token request on the service. Returns an OAuthToken on success.
+   * Throws an exception if the request fails.
+   *
+   * @access public
+   *
+   * @return OAuthToken An unauthorized request token
+   **/
+  public function requestToken() {
+    /* Set the token to null just in case the user called setToken. */
+    $this->token = null;
+
+    $result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
+                                  array('omb_listener' => $this->listener_uri));
+    if ($result->status != 200) {
+      throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
+                                                  $result);
+    }
+    parse_str($result->body, $return);
+    if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
+      throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
+                                                  $result);
+    }
+    $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
+    return $this->token;
+  }
+
+  /**
+   *
+   * Request authorization
+   *
+   * Returns an URL which equals to an authorization request. The end user
+   * should be redirected to this location to perform authorization.
+   * The $finish_url should be a local resource which invokes
+   * OMB_Consumer::finishAuthorization on request.
+   *
+   * @param OMB_Profile $profile    An OMB_Profile object representing the
+   *                                soon-to-be subscribed (i. e. local) user
+   * @param string      $finish_url Target location after successful
+   *                                authorization
+   *
+   * @access public
+   *
+   * @return string An URL representing an authorization request
+   **/
+  public function requestAuthorization($profile, $finish_url) {
+    if ($this->performLegacyAuthRequest) {
+      $params = $profile->asParameters('omb_listenee', false);
+      $params['omb_listener'] = $this->listener_uri;
+      $params['oauth_callback'] = $finish_url;
+
+      $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
+    } else {
+
+      $params = array(
+                'oauth_callback' => $finish_url,
+                'oauth_token'    => $this->token->key,
+                'omb_version'    => OMB_VERSION,
+                'omb_listener'   => $this->listener_uri);
+
+      $params = array_merge($profile->asParameters('omb_listenee', false). $params);
+
+      /* Build result URL. */
+      $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE];
+      $url .= (strrpos($url, '?') === false ? '?' : '&');
+      foreach ($params as $k => $v) {
+        $url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&';
+      }
+    }
+
+    $this->listenee_uri = $profile->getIdentifierURI();
+
+    return $url;
+  }
+
+  /**
+   * Finish authorization
+   *
+   * Finish the subscription process by converting the received and authorized
+   * request token into an access token. After that, the subscriber’s profile
+   * and the subscription are stored in the database.
+   * Expects an OAuthRequest in query parameters.
+   * Throws exceptions on failure.
+   *
+   * @access public
+   **/
+  public function finishAuthorization() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+    $req = OAuthRequest::from_request();
+    if ($req->get_parameter('oauth_token') !=
+          $this->token->key) {
+      /* That’s not the token I wanted to get authorized. */
+      throw new OAuthException('The authorized token does not equal the ' .
+                               'submitted token.');
+    }
+
+    if ($req->get_parameter('omb_version') != OMB_VERSION) {
+      throw new OMB_RemoteServiceException('The remote service uses an ' .
+                                           'unsupported OMB version');
+    }
+
+    /* Construct the profile to validate it. */
+
+    /* Fix OMB bug. Listener URI is not passed. */
+    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+      $params = $_POST;
+    } else {
+      $params = $_GET;
+    }
+    $params['omb_listener'] = $this->listener_uri;
+
+    require_once 'profile.php';
+    $listener = OMB_Profile::fromParameters($params, 'omb_listener');
+
+    /* Ask the remote service to convert the authorized request token into an
+       access token. */
+
+    $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
+    if ($result->status != 200) {
+      throw new OAuthException('Could not get access token');
+    }
+
+    parse_str($result->body, $return);
+    if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
+      throw new OAuthException('Could not get access token');
+    }
+    $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
+
+    /* Subscription is finished and valid. Now store the new subscriber and the
+       subscription in the database. */
+
+    $this->datastore->saveProfile($listener);
+    $this->datastore->saveSubscription($this->listener_uri,
+                                       $this->listenee_uri,
+                                       $this->token);
+  }
+
+  /**
+   * Return the URI identifying the listener
+   *
+   * Returns the URI for the OMB user who tries to subscribe or already has
+   * subscribed our user. This method is a workaround for a serious OMB flaw:
+   * The Listener URI is not passed in the finishauthorization call.
+   *
+   * @access public
+   *
+   * @return string the listener’s URI
+   **/
+  public function getListenerURI() {
+    return $this->listener_uri;
+  }
+
+  /**
+   * Inform the service about a profile update
+   *
+   * Sends an updated profile to the service.
+   *
+   * @param OMB_Profile $profile The profile that has changed
+   *
+   * @access public
+   **/
+  public function updateProfile($profile) {
+    $params = $profile->asParameters('omb_listenee', true);
+    $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
+  }
+
+  /**
+   * Inform the service about a new notice
+   *
+   * Sends a notice to the service.
+   *
+   * @param OMB_Notice $notice The notice
+   *
+   * @access public
+   **/
+  public function postNotice($notice) {
+    $params = $notice->asParameters();
+    $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
+    $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']);
+  }
+
+  /**
+   * Set the token member variable
+   *
+   * Initializes the token based on given token and secret token.
+   *
+   * @param string $token  The token
+   * @param string $secret The secret token
+   *
+   * @access public
+   **/
+  public function setToken($token, $secret) {
+    $this->token = new OAuthToken($token, $secret);
+  }
+
+  /**
+   * Prepare an OAuthRequest object
+   *
+   * Creates an OAuthRequest object mapping the request specified by the
+   * parameters.
+   *
+   * @param string $action_uri The URI specifying the target service
+   * @param array  $params     Additional parameters for the service call
+   * @param string $method     The HTTP method used to call the service
+   *                           ('POST' or 'GET', usually)
+   *
+   * @access protected
+   *
+   * @return OAuthRequest the prepared request
+   **/
+  protected function prepareAction($action_uri, $params, $method) {
+    $url = $this->services[$action_uri];
+
+    $url_params = array();
+    parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
+
+    /* Add OMB version. */
+    $url_params['omb_version'] = OMB_VERSION;
+
+    /* Add user-defined parameters. */
+    $url_params = array_merge($url_params, $params);
+
+    $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
+                                      $this->token, $method, $url, $url_params);
+
+    /* Sign the request. */
+    $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
+                                           $this->oauth_consumer, $this->token);
+
+    return $req;
+  }
+
+  /**
+   * Perform a service call
+   *
+   * Creates an OAuthRequest object and execute the mapped call as POST request.
+   *
+   * @param string $action_uri The URI specifying the target service
+   * @param array  $params     Additional parameters for the service call
+   *
+   * @access protected
+   *
+   * @return Auth_Yadis_HTTPResponse The POST request response
+   **/
+  protected function performAction($action_uri, $params) {
+    $req = $this->prepareAction($action_uri, $params, 'POST');
+
+    /* Return result page. */
+    return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
+  }
+
+  /**
+   * Perform an OMB action
+   *
+   * Executes an OMB action – to date, it’s one of updateProfile or postNotice.
+   *
+   * @param string $action_uri   The URI specifying the target service
+   * @param array  $params       Additional parameters for the service call
+   * @param string $listenee_uri The URI identifying the local user for whom
+   *                             the action is performed
+   *
+   * @access protected
+   **/
+  protected function performOMBAction($action_uri, $params, $listenee_uri) {
+    $result = $this->performAction($action_uri, $params);
+    if ($result->status == 403) {
+      /* The remote user unsubscribed us. */
+      $this->datastore->deleteSubscription($this->listener_uri, $listenee_uri);
+    } else if ($result->status != 200 ||
+               strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
+      /* The server signaled an error or sent an incorrect response. */
+      throw OMB_RemoteServiceException::fromYadis($action_uri, $result);
+    }
+  }
+}
diff --git a/extlib/libomb/service_provider.php b/extlib/libomb/service_provider.php
new file mode 100755 (executable)
index 0000000..b3ad537
--- /dev/null
@@ -0,0 +1,411 @@
+<?php
+
+require_once 'constants.php';
+require_once 'remoteserviceexception.php';
+require_once 'helper.php';
+
+/**
+ * OMB service realization
+ *
+ * This class realizes a complete, simple OMB service.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+class OMB_Service_Provider {
+  protected $user; /* An OMB_Profile representing the user */
+  protected $datastore; /* AN OMB_Datastore */
+
+  protected $remote_user; /* An OMB_Profile representing the remote user during
+                            the authorization process */
+
+  protected $oauth_server; /* An OAuthServer; should only be accessed via
+                              getOAuthServer. */
+
+  /**
+   * Initialize an OMB_Service_Provider object
+   *
+   * Constructs an OMB_Service_Provider instance that provides OMB services
+   * referring to a particular user.
+   *
+   * @param OMB_Profile   $user         An OMB_Profile; mandatory for XRDS
+   *                                    output, user auth handling and OMB
+   *                                    action performing
+   * @param OMB_Datastore $datastore    An OMB_Datastore; mandatory for
+   *                                    everything but XRDS output
+   * @param OAuthServer   $oauth_server An OAuthServer; used for token writing
+   *                                    and OMB action handling; will use
+   *                                    default value if not set
+   *
+   * @access public
+   **/
+  public function __construct ($user = null, $datastore = null, $oauth_server = null) {
+    $this->user = $user;
+    $this->datastore = $datastore;
+    $this->oauth_server = $oauth_server;
+  }
+
+  public function getRemoteUser() {
+    return $this->remote_user;
+  }
+
+  /**
+   * Write a XRDS document
+   *
+   * Writes a XRDS document specifying the OMB service. Optionally uses a
+   * given object of a class implementing OMB_XRDS_Writer for output. Else
+   * OMB_Plain_XRDS_Writer is used.
+   *
+   * @param OMB_XRDS_Mapper $xrds_mapper An object mapping actions to URLs
+   * @param OMB_XRDS_Writer $xrds_writer Optional; The OMB_XRDS_Writer used to
+   *                                     write the XRDS document
+   *
+   * @access public
+   *
+   * @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer
+   *               returns nothing.
+   **/
+  public function writeXRDS($xrds_mapper, $xrds_writer = null) {
+    if ($xrds_writer == null) {
+        require_once 'plain_xrds_writer.php';
+        $xrds_writer = new OMB_Plain_XRDS_Writer();
+    }
+    return $xrds_writer->writeXRDS($this->user, $xrds_mapper);
+  }
+
+  /**
+   * Echo a request token
+   *
+   * Outputs an unauthorized request token for the query found in $_GET or
+   * $_POST.
+   *
+   * @access public
+   **/
+  public function writeRequestToken() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+    echo $this->getOAuthServer()->fetch_request_token(OAuthRequest::from_request());
+  }
+
+  /**
+   * Handle an user authorization request.
+   *
+   * Parses an authorization request. This includes OAuth and OMB verification.
+   * Throws exceptions on failures. Returns an OMB_Profile object representing
+   * the remote user.
+   *
+   * @access public
+   *
+   * @return OMB_Profile The profile of the soon-to-be subscribed, i. e. remote
+   *                     user
+   **/
+  public function handleUserAuth() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+
+    /* Verify the request token. */
+
+    $this->token = $this->datastore->lookup_token(null, "request", $_GET['oauth_token']);
+    if (is_null($this->token)) {
+      throw new OAuthException('The given request token has not been issued ' .
+                               'by this service.');
+    }
+
+    /* Verify the OMB part. */
+
+    if ($_GET['omb_version'] !== OMB_VERSION) {
+      throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                   'Wrong OMB version ' . $_GET['omb_version']);
+    }
+
+    if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
+      throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                 'Wrong OMB listener ' . $_GET['omb_listener']);
+    }
+
+    foreach (array('omb_listenee', 'omb_listenee_profile',
+                   'omb_listenee_nickname', 'omb_listenee_license') as $param) {
+      if (!isset($_GET[$param]) || is_null($_GET[$param])) {
+        throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
+                                       "Required parameter '$param' not found");
+      }
+    }
+
+    /* Store given callback for later use. */
+    if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') {
+      $this->callback = $_GET['oauth_callback'];
+    }
+    $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
+
+    return $this->remote_user;
+  }
+
+  /**
+   * Continue the OAuth dance after user authorization
+   *
+   * Performs the appropriate actions after user answered the authorization
+   * request.
+   *
+   * @param bool $accepted Whether the user granted authorization
+   *
+   * @access public
+   *
+   * @return array A two-component array with the values:
+   *                - callback The callback URL or null if none given
+   *                - token    The authorized request token or null if not
+   *                           authorized.
+   **/
+  public function continueUserAuth($accepted) {
+    $callback = $this->callback;
+    if (!$accepted) {
+      $this->datastore->revoke_token($this->token->key);
+      $this->token = null;
+      /* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way
+               laconica works. Moreover I don’t know the right way either. */
+
+    } else {
+      $this->datastore->authorize_token($this->token->key);
+      $this->datastore->saveProfile($this->remote_user);
+      $this->datastore->saveSubscription($this->user->getIdentifierURI(),
+                          $this->remote_user->getIdentifierURI(), $this->token);
+
+      if (!is_null($this->callback)) {
+        /* Callback wants to get some informations as well. */
+        $params = $this->user->asParameters('omb_listener', false);
+
+        $params['oauth_token'] = $this->token->key;
+        $params['omb_version'] = OMB_VERSION;
+
+        $callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
+        foreach ($params as $k => $v) {
+          $callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
+                       OAuthUtil::urlencode_rfc3986($v) . '&';
+        }
+      }
+    }
+    return array($callback, $this->token);
+  }
+
+  /**
+   * Echo an access token
+   *
+   * Outputs an access token for the query found in $_GET or $_POST.
+   *
+   * @access public
+   **/
+  public function writeAccessToken() {
+    OMB_Helper::removeMagicQuotesFromRequest();
+    echo $this->getOAuthServer()->fetch_access_token(OAuthRequest::from_request());
+  }
+
+  /**
+   * Handle an updateprofile request
+   *
+   * Handles an updateprofile request posted to this service. Updates the
+   * profile through the OMB_Datastore.
+   *
+   * @access public
+   *
+   * @return OMB_Profile The updated profile
+   **/
+  public function handleUpdateProfile() {
+    list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE);
+    $profile->updateFromParameters($req->get_parameters(), 'omb_listenee');
+    $this->datastore->saveProfile($profile);
+    $this->finishOMBRequest();
+    return $profile;
+  }
+
+  /**
+   * Handle a postnotice request
+   *
+   * Handles a postnotice request posted to this service.
+   *
+   * @access public
+   *
+   * @return OMB_Notice The received notice
+   **/
+  public function handlePostNotice() {
+    list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
+    require_once 'notice.php';
+    $notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
+    $this->datastore->saveNotice($notice);
+    $this->finishOMBRequest();
+    return $notice;
+  }
+
+  /**
+   * Handle an OMB request
+   *
+   * Performs common OMB request handling.
+   *
+   * @param string $uri The URI defining the OMB endpoint being served
+   *
+   * @access protected
+   *
+   * @return array(OAuthRequest, OMB_Profile)
+   **/
+  protected function handleOMBRequest($uri) {
+
+    OMB_Helper::removeMagicQuotesFromRequest();
+    $req = OAuthRequest::from_request();
+    $listenee =  $req->get_parameter('omb_listenee');
+
+    try {
+        list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
+    } catch (OAuthException $e) {
+      header('HTTP/1.1 403 Forbidden');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'Revoked accesstoken for ' . $listenee);
+    }
+
+    $version = $req->get_parameter('omb_version');
+    if ($version !== OMB_VERSION) {
+      header('HTTP/1.1 400 Bad Request');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'Wrong OMB version ' . $version);
+    }
+
+    $profile = $this->datastore->getProfile($listenee);
+    if (is_null($profile)) {
+      header('HTTP/1.1 400 Bad Request');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'Unknown remote profile ' . $listenee);
+    }
+
+    $subscribers = $this->datastore->getSubscriptions($listenee);
+    if (count($subscribers) === 0) {
+      header('HTTP/1.1 403 Forbidden');
+      throw OMB_RemoteServiceException::forRequest($uri,
+                                   'No subscriber for ' . $listenee);
+    }
+
+    return array($req, $profile);
+  }
+
+  /**
+   * Finishes an OMB request handling
+   *
+   * Performs common OMB request handling finishing.
+   *
+   * @access protected
+   **/
+  protected function finishOMBRequest() {
+    header('HTTP/1.1 200 OK');
+    header('Content-type: text/plain');
+    /* There should be no clutter but the version. */
+    echo "omb_version=" . OMB_VERSION;
+  }
+
+  /**
+   * Return an OAuthServer
+   *
+   * Checks whether the OAuthServer is null. If so, initializes it with a
+   * default value. Returns the OAuth server.
+   *
+   * @access protected
+   **/
+  protected function getOAuthServer() {
+    if (is_null($this->oauth_server)) {
+      $this->oauth_server = new OAuthServer($this->datastore);
+      $this->oauth_server->add_signature_method(
+                                          new OAuthSignatureMethod_HMAC_SHA1());
+    }
+    return $this->oauth_server;
+  }
+
+  /**
+   * Publish a notice
+   *
+   * Posts an OMB notice. This includes storing the notice and posting it to
+   * subscribed users.
+   *
+   * @param OMB_Notice $notice The new notice
+   *
+   * @access public
+   *
+   * @return array An array mapping subscriber URIs to the exception posting to
+   *               them has raised; Empty array if no exception occured
+   **/
+  public function postNotice($notice) {
+    $uri = $this->user->getIdentifierURI();
+
+    /* $notice is passed by reference and may change. */
+    $this->datastore->saveNotice($notice);
+    $subscribers = $this->datastore->getSubscriptions($uri);
+
+    /* No one to post to. */
+    if (is_null($subscribers)) {
+        return array();
+    }
+
+    require_once 'service_consumer.php';
+
+    $err = array();
+    foreach($subscribers as $subscriber) {
+      try {
+        $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
+        $service->setToken($subscriber['token'], $subscriber['secret']);
+        $service->postNotice($notice);
+      } catch (Exception $e) {
+        $err[$subscriber['uri']] = $e;
+        continue;
+      }
+    }
+    return $err;
+  }
+
+  /**
+   * Publish a profile update
+   *
+   * Posts the current profile as an OMB profile update. This includes updating
+   * the stored profile and posting it to subscribed users.
+   *
+   * @access public
+   *
+   * @return array An array mapping subscriber URIs to the exception posting to
+   *               them has raised; Empty array if no exception occured
+   **/
+  public function updateProfile() {
+    $uri = $this->user->getIdentifierURI();
+
+    $this->datastore->saveProfile($this->user);
+    $subscribers = $this->datastore->getSubscriptions($uri);
+
+    /* No one to post to. */
+    if (is_null($subscribers)) {
+        return array();
+    }
+
+    require_once 'service_consumer.php';
+
+    $err = array();
+    foreach($subscribers as $subscriber) {
+      try {
+        $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
+        $service->setToken($subscriber['token'], $subscriber['secret']);
+        $service->updateProfile($this->user);
+      } catch (Exception $e) {
+        $err[$subscriber['uri']] = $e;
+        continue;
+      }
+    }
+    return $err;
+  }
+}
diff --git a/extlib/libomb/unsupportedserviceexception.php b/extlib/libomb/unsupportedserviceexception.php
new file mode 100755 (executable)
index 0000000..4dab63e
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Exception stating that a requested service is not available
+ *
+ * This exception is raised when OMB_Service is asked to call a service the remote
+ * server does not provide.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+class OMB_UnsupportedServiceException extends Exception {
+
+}
+?>
diff --git a/extlib/libomb/xrds_mapper.php b/extlib/libomb/xrds_mapper.php
new file mode 100755 (executable)
index 0000000..7552154
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Map XRDS actions to URLs
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+interface OMB_XRDS_Mapper {
+  public function getURL($action);
+}
+?>
diff --git a/extlib/libomb/xrds_writer.php b/extlib/libomb/xrds_writer.php
new file mode 100755 (executable)
index 0000000..31b451b
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Write OMB-specific XRDS
+ *
+ * This interface specifies classes which write the XRDS file announcing
+ * the OMB server. An instance of an implementing class should be passed to
+ * OMB_Service_Provider->writeXRDS.
+ *
+ * PHP version 5
+ *
+ * LICENSE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package   OMB
+ * @author    Adrian Lang <mail@adrianlang.de>
+ * @copyright 2009 Adrian Lang
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
+ **/
+
+interface OMB_XRDS_Writer {
+  public function writeXRDS($user, $mapper);
+}
+?>
index f224c6c2213ed3324ad49d3f87567abc4ef988fd..87d8cf2137b197718d93b4d5803502e5c50f867f 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
-require_once(INSTALLDIR.'/lib/omb.php');
+require_once 'libomb/datastore.php';
 
-class LaconicaOAuthDataStore extends OAuthDataStore
+class LaconicaDataStore extends OMB_Datastore
 {
 
     // We keep a record of who's contacted us
-
     function lookup_consumer($consumer_key)
     {
         $con = Consumer::staticGet('consumer_key', $consumer_key);
@@ -44,7 +45,9 @@ class LaconicaOAuthDataStore extends OAuthDataStore
     function lookup_token($consumer, $token_type, $token_key)
     {
         $t = new Token();
-        $t->consumer_key = $consumer->key;
+        if (!is_null($consumer)) {
+            $t->consumer_key = $consumer->key;
+        }
         $t->tok = $token_key;
         $t->type = ($token_type == 'access') ? 1 : 0;
         if ($t->find(true)) {
@@ -154,4 +157,348 @@ class LaconicaOAuthDataStore extends OAuthDataStore
     {
         return $this->new_access_token($consumer);
     }
+
+
+    /**
+     * Revoke specified OAuth token
+     *
+     * Revokes the authorization token specified by $token_key.
+     * Throws exceptions in case of error.
+     *
+     * @param string $token_key The token to be revoked
+     *
+     * @access public
+     **/
+    public function revoke_token($token_key) {
+        $rt = new Token();
+        $rt->tok = $token_key;
+        $rt->type = 0;
+        $rt->state = 0;
+        if (!$rt->find(true)) {
+            throw new Exception('Tried to revoke unknown token');
+        }
+        if (!$rt->delete()) {
+            throw new Exception('Failed to delete revoked token');
+        }
+    }
+
+    /**
+     * Authorize specified OAuth token
+     *
+     * Authorizes the authorization token specified by $token_key.
+     * Throws exceptions in case of error.
+     *
+     * @param string $token_key The token to be authorized
+     *
+     * @access public
+     **/
+    public function authorize_token($token_key) {
+        $rt = new Token();
+        $rt->tok = $token_key;
+        $rt->type = 0;
+        $rt->state = 0;
+        if (!$rt->find(true)) {
+            throw new Exception('Tried to authorize unknown token');
+        }
+        $orig_rt = clone($rt);
+        $rt->state = 1; # Authorized but not used
+        if (!$rt->update($orig_rt)) {
+            throw new Exception('Failed to authorize token');
+        }
+    }
+
+    /**
+     * Get profile by identifying URI
+     *
+     * Returns an OMB_Profile object representing the OMB profile identified by
+     * $identifier_uri.
+     * Returns null if there is no such OMB profile.
+     * Throws exceptions in case of other error.
+     *
+     * @param string $identifier_uri The OMB identifier URI specifying the
+     *                               requested profile
+     *
+     * @access public
+     *
+     * @return OMB_Profile The corresponding profile
+     **/
+    public function getProfile($identifier_uri) {
+        /* getProfile is only used for remote profiles by libomb.
+           TODO: Make it work with local ones anyway. */
+        $remote = Remote_profile::staticGet('uri', $identifier_uri);
+        if (!$remote) throw new Exception('No such remote profile');
+        $profile = Profile::staticGet('id', $remote->id);
+        if (!$profile) throw new Exception('No profile for remote user');
+
+        require_once INSTALLDIR.'/lib/omb.php';
+        return profile_to_omb_profile($identifier_uri, $profile);
+    }
+
+    /**
+     * Save passed profile
+     *
+     * Stores the OMB profile $profile. Overwrites an existing entry.
+     * Throws exceptions in case of error.
+     *
+     * @param OMB_Profile $profile   The OMB profile which should be saved
+     *
+     * @access public
+     **/
+    public function saveProfile($omb_profile) {
+        if (common_profile_url($omb_profile->getNickname()) ==
+                                                $omb_profile->getProfileURL()) {
+            throw new Exception('Not implemented');
+        } else {
+            $remote = Remote_profile::staticGet('uri', $omb_profile->getIdentifierURI());
+
+            if ($remote) {
+                $exists = true;
+                $profile = Profile::staticGet($remote->id);
+                $orig_remote = clone($remote);
+                $orig_profile = clone($profile);
+                # XXX: compare current postNotice and updateProfile URLs to the ones
+                # stored in the DB to avoid (possibly...) above attack
+            } else {
+                $exists = false;
+                $remote = new Remote_profile();
+                $remote->uri = $omb_profile->getIdentifierURI();
+                $profile = new Profile();
+            }
+
+            $profile->nickname = $omb_profile->getNickname();
+            $profile->profileurl = $omb_profile->getProfileURL();
+
+            $fullname = $omb_profile->getFullname();
+            $profile->fullname = is_null($fullname) ? '' : $fullname;
+            $homepage = $omb_profile->getHomepage();
+            $profile->homepage = is_null($homepage) ? '' : $homepage;
+            $bio = $omb_profile->getBio();
+            $profile->bio = is_null($bio) ? '' : $bio;
+            $location = $omb_profile->getLocation();
+            $profile->location = is_null($location) ? '' : $location;
+
+            if ($exists) {
+                $profile->update($orig_profile);
+            } else {
+                $profile->created = DB_DataObject_Cast::dateTime(); # current time
+                $id = $profile->insert();
+                if (!$id) {
+                    throw new Exception(_('Error inserting new profile'));
+                }
+                $remote->id = $id;
+            }
+
+            $avatar_url = $omb_profile->getAvatarURL();
+            if ($avatar_url) {
+                if (!$this->add_avatar($profile, $avatar_url)) {
+                    throw new Exception(_('Error inserting avatar'));
+                }
+            } else {
+                $avatar = $profile->getOriginalAvatar();
+                if($avatar) $avatar->delete();
+                $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+                if($avatar) $avatar->delete();
+                $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+                if($avatar) $avatar->delete();
+                $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
+                if($avatar) $avatar->delete();
+            }
+
+            if ($exists) {
+                if (!$remote->update($orig_remote)) {
+                    throw new Exception(_('Error updating remote profile'));
+                }
+            } else {
+                $remote->created = DB_DataObject_Cast::dateTime(); # current time
+                if (!$remote->insert()) {
+                    throw new Exception(_('Error inserting remote profile'));
+                }
+            }
+        }
+    }
+
+    function add_avatar($profile, $url)
+    {
+        $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
+        copy($url, $temp_filename);
+        $imagefile = new ImageFile($profile->id, $temp_filename);
+        $filename = Avatar::filename($profile->id,
+                                     image_type_to_extension($imagefile->type),
+                                     null,
+                                     common_timestamp());
+        rename($temp_filename, Avatar::path($filename));
+        return $profile->setOriginal($filename);
+    }
+
+    /**
+     * Save passed notice
+     *
+     * Stores the OMB notice $notice. The datastore may change the passed notice.
+     * This might by neccessary for URIs depending on a database key. Note that
+     * it is the user’s duty to present a mechanism for his OMB_Datastore to
+     * appropriately change his OMB_Notice.
+     * Throws exceptions in case of error.
+     *
+     * @param OMB_Notice $notice The OMB notice which should be saved
+     *
+     * @access public
+     **/
+    public function saveNotice(&$omb_notice) {
+        if (Notice::staticGet('uri', $omb_notice->getIdentifierURI())) {
+            throw new Exception(_('Duplicate notice'));
+        }
+        $author_uri = $omb_notice->getAuthor()->getIdentifierURI();
+        common_log(LOG_DEBUG, $author_uri, __FILE__);
+        $author = Remote_profile::staticGet('uri', $author_uri);
+        if (!$author) {
+            $author = User::staticGet('uri', $author_uri);
+        }
+        if (!$author) {
+            throw new Exception('No such user');
+        }
+
+        common_log(LOG_DEBUG, print_r($author, true), __FILE__);
+
+        $notice = Notice::saveNew($author->id,
+                                  $omb_notice->getContent(),
+                                  'omb',
+                                  false,
+                                  null,
+                                  $omb_notice->getIdentifierURI());
+        if (is_string($notice)) {
+            throw new Exception($notice);
+        }
+        common_broadcast_notice($notice, true);
+    }
+
+    /**
+     * Get subscriptions of a given profile
+     *
+     * Returns an array containing subscription informations for the specified
+     * profile. Every array entry should in turn be an array with keys
+     *   'uri´: The identifier URI of the subscriber
+     *   'token´: The subscribe token
+     *   'secret´: The secret token
+     * Throws exceptions in case of error.
+     *
+     * @param string $subscribed_user_uri The OMB identifier URI specifying the
+     *                                    subscribed profile
+     *
+     * @access public
+     *
+     * @return mixed An array containing the subscriptions or 0 if no
+     *               subscription has been found.
+     **/
+    public function getSubscriptions($subscribed_user_uri) {
+        $sub = new Subscription();
+
+        $user = $this->_getAnyProfile($subscribed_user_uri);
+
+        $sub->subscribed = $user->id;
+
+        if (!$sub->find(true)) {
+            return 0;
+        }
+
+        /* Since we do not use OMB_Service_Provider’s action methods, there
+           is no need to actually return the subscriptions. */
+        return 1;
+    }
+
+    private function _getAnyProfile($uri)
+    {
+        $user = Remote_profile::staticGet('uri', $uri);
+        if (!$user) {
+            $user = User::staticGet('uri', $uri);
+        }
+        if (!$user) {
+            throw new Exception('No such user');
+        }
+        return $user;
+    }
+
+    /**
+     * Delete a subscription
+     *
+     * Deletes the subscription from $subscriber_uri to $subscribed_user_uri.
+     * Throws exceptions in case of error.
+     *
+     * @param string $subscriber_uri      The OMB identifier URI specifying the
+     *                                    subscribing profile
+     *
+     * @param string $subscribed_user_uri The OMB identifier URI specifying the
+     *                                    subscribed profile
+     *
+     * @access public
+     **/
+    public function deleteSubscription($subscriber_uri, $subscribed_user_uri)
+    {
+        $sub = new Subscription();
+
+        $subscribed = $this->_getAnyProfile($subscribed_user_uri);
+        $subscriber = $this->_getAnyProfile($subscriber_uri);
+
+        $sub->subscribed = $subscribed->id;
+        $sub->subscriber = $subscriber->id;
+
+        $sub->delete();
+    }
+
+    /**
+     * Save a subscription
+     *
+     * Saves the subscription from $subscriber_uri to $subscribed_user_uri.
+     * Throws exceptions in case of error.
+     *
+     * @param string     $subscriber_uri      The OMB identifier URI specifying
+     *                                        the subscribing profile
+     *
+     * @param string     $subscribed_user_uri The OMB identifier URI specifying
+     *                                        the subscribed profile
+     * @param OAuthToken $token               The access token
+     *
+     * @access public
+     **/
+    public function saveSubscription($subscriber_uri, $subscribed_user_uri,
+                                                                       $token)
+    {
+        $sub = new Subscription();
+
+        $subscribed = $this->_getAnyProfile($subscribed_user_uri);
+        $subscriber = $this->_getAnyProfile($subscriber_uri);
+
+        $sub->subscribed = $subscribed->id;
+        $sub->subscriber = $subscriber->id;
+
+        $sub_exists = $sub->find(true);
+
+        if ($sub_exists) {
+            $orig_sub = clone($sub);
+        } else {
+            $sub->created = DB_DataObject_Cast::dateTime();
+        }
+
+        $sub->token  = $token->key;
+        $sub->secret = $token->secret;
+
+        if ($sub_exists) {
+            $result = $sub->update($orig_sub);
+        } else {
+            $result = $sub->insert();
+        }
+
+        if (!$result) {
+            common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
+            throw new Exception(_('Couldn\'t insert new subscription.'));
+            return;
+        }
+
+        /* Notify user, if necessary. */
+
+        if ($subscribed instanceof User) {
+            mail_subscribe_notify_profile($subscribed,
+                                          Profile::staticGet($subscriber->id));
+        }
+    }
 }
+?>
index 4f6a9609541ded863edcc62edef1ffcff10f4283..b9d0eef64e6291e69e38d505590acd00a623f45c 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
-
-require_once('OAuth.php');
-require_once(INSTALLDIR.'/lib/oauthstore.php');
-
-require_once(INSTALLDIR.'/classes/Consumer.php');
-require_once(INSTALLDIR.'/classes/Nonce.php');
-require_once(INSTALLDIR.'/classes/Token.php');
-
-require_once('Auth/Yadis/Yadis.php');
-
-define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
-define('OMB_NAMESPACE', 'http://openmicroblogging.org/protocol/0.1');
-define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
-define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
-define('OMB_ENDPOINT_UPDATEPROFILE', OMB_NAMESPACE.'/updateProfile');
-define('OMB_ENDPOINT_POSTNOTICE', OMB_NAMESPACE.'/postNotice');
-define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
-define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
-define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
-define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
-define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
-define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
-define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
+require_once INSTALLDIR.'/lib/oauthstore.php';
+require_once 'OAuth.php';
+require_once 'libomb/constants.php';
+require_once 'libomb/service_consumer.php';
+require_once 'libomb/notice.php';
+require_once 'libomb/profile.php';
+require_once 'Auth/Yadis/Yadis.php';
 
 function omb_oauth_consumer()
 {
     static $con = null;
-    if (!$con) {
+    if (is_null($con)) {
         $con = new OAuthConsumer(common_root_url(), '');
     }
     return $con;
@@ -55,7 +41,7 @@ function omb_oauth_consumer()
 function omb_oauth_server()
 {
     static $server = null;
-    if (!$server) {
+    if (is_null($server)) {
         $server = new OAuthServer(omb_oauth_datastore());
         $server->add_signature_method(omb_hmac_sha1());
     }
@@ -65,8 +51,8 @@ function omb_oauth_server()
 function omb_oauth_datastore()
 {
     static $store = null;
-    if (!$store) {
-        $store = new LaconicaOAuthDataStore();
+    if (is_null($store)) {
+        $store = new LaconicaDataStore();
     }
     return $store;
 }
@@ -74,57 +60,18 @@ function omb_oauth_datastore()
 function omb_hmac_sha1()
 {
     static $hmac_method = null;
-    if (!$hmac_method) {
+    if (is_null($hmac_method)) {
         $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
     }
     return $hmac_method;
 }
 
-function omb_get_services($xrd, $type)
+function omb_broadcast_notice($notice)
 {
-    return $xrd->services(array(omb_service_filter($type)));
-}
 
-function omb_service_filter($type)
-{
-    return create_function('$s',
-                           'return omb_match_service($s, \''.$type.'\');');
-}
+    $omb_notice = notice_to_omb_notice($notice);
 
-function omb_match_service($service, $type)
-{
-    return in_array($type, $service->getTypes());
-}
-
-function omb_service_uri($service)
-{
-    if (!$service) {
-        return null;
-    }
-    $uris = $service->getURIs();
-    if (!$uris) {
-        return null;
-    }
-    return $uris[0];
-}
-
-function omb_local_id($service)
-{
-    if (!$service) {
-        return null;
-    }
-    $els = $service->getElements('xrd:LocalID');
-    if (!$els) {
-        return null;
-    }
-    $el = $els[0];
-    return $service->parser->content($el);
-}
-
-function omb_broadcast_remote_subscribers($notice)
-{
-
-    # First, get remote users subscribed to this profile
+    /* Get remote users subscribed to this profile. */
     $rp = new Remote_profile();
 
     $rp->query('SELECT postnoticeurl, token, secret ' .
@@ -135,170 +82,148 @@ function omb_broadcast_remote_subscribers($notice)
     $posted = array();
 
     while ($rp->fetch()) {
-        if (!$posted[$rp->postnoticeurl]) {
-            common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl);
-            if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) {
-                common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl);
-                $posted[$rp->postnoticeurl] = true;
-            } else {
-                common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl);
-            }
+        if (isset($posted[$rp->postnoticeurl])) {
+            /* We already posted to this url. */
+            continue;
         }
-    }
-
-    $rp->free();
-    unset($rp);
+        common_debug('Posting to ' . $rp->postnoticeurl, __FILE__);
+
+        /* Post notice. */
+        $service = new Laconica_OMB_Service_Consumer(
+                     array(OMB_ENDPOINT_POSTNOTICE => $rp->postnoticeurl));
+        try {
+            $service->setToken($rp->token, $rp->secret);
+            $service->postNotice($omb_notice);
+        } catch (Exception $e) {
+            common_log(LOG_ERR, 'Failed posting to ' . $rp->postnoticeurl);
+            common_log(LOG_ERR, 'Error status '.$e);
+            continue;
+        }
+        $posted[$rp->postnoticeurl] = true;
 
-    return true;
-}
+        common_debug('Finished to ' . $rp->postnoticeurl, __FILE__);
+    }
 
-function omb_post_notice($notice, $remote_profile, $subscription)
-{
-    return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret);
+    return;
 }
 
-function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
+function omb_broadcast_profile($profile)
 {
-    $user = User::staticGet('id', $notice->profile_id);
+    $user = User::staticGet('id', $profile->id);
 
     if (!$user) {
         return false;
     }
 
-    $con = omb_oauth_consumer();
-
-    $token = new OAuthToken($tk, $secret);
-
-    $url = $postnoticeurl;
-    $parsed = parse_url($url);
-    $params = array();
-    parse_str($parsed['query'], $params);
+    $profile = $user->getProfile();
 
-    $req = OAuthRequest::from_consumer_and_token($con, $token,
-                                                 'POST', $url, $params);
+    $omb_profile = profile_to_omb_profile($user->uri, $profile, true);
 
-    $req->set_parameter('omb_version', OMB_VERSION_01);
-    $req->set_parameter('omb_listenee', $user->uri);
-    $req->set_parameter('omb_notice', $notice->uri);
-    $req->set_parameter('omb_notice_content', $notice->content);
-    $req->set_parameter('omb_notice_url', common_local_url('shownotice',
-                                                           array('notice' =>
-                                                                 $notice->id)));
-    $req->set_parameter('omb_notice_license', common_config('license', 'url'));
-
-    $user->free();
-    unset($user);
+    /* Get remote users subscribed to this profile. */
+    $rp = new Remote_profile();
 
-    $req->sign_request(omb_hmac_sha1(), $con, $token);
+    $rp->query('SELECT updateprofileurl, token, secret ' .
+               'FROM subscription JOIN remote_profile ' .
+               'ON subscription.subscriber = remote_profile.id ' .
+               'WHERE subscription.subscribed = ' . $profile->id . ' ');
 
-    # We re-use this tool's fetcher, since it's pretty good
+    $posted = array();
 
-    $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+    while ($rp->fetch()) {
+        if (isset($posted[$rp->updateprofileurl])) {
+            /* We already posted to this url. */
+            continue;
+        }
+        common_debug('Posting to ' . $rp->updateprofileurl, __FILE__);
+
+        /* Update profile. */
+        $service = new Laconica_OMB_Service_Consumer(
+                     array(OMB_ENDPOINT_UPDATEPROFILE => $rp->updateprofileurl));
+        try {
+            $service->setToken($rp->token, $rp->secret);
+            $service->updateProfile($omb_profile);
+        } catch (Exception $e) {
+            common_log(LOG_ERR, 'Failed posting to ' . $rp->updateprofileurl);
+            common_log(LOG_ERR, 'Error status '.$e);
+            continue;
+        }
+        $posted[$rp->updateprofileurl] = true;
 
-    if (!$fetcher) {
-        common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__);
-        return false;
+        common_debug('Finished to ' . $rp->updateprofileurl, __FILE__);
     }
 
-    $result = $fetcher->post($req->get_normalized_http_url(),
-                             $req->to_postdata(),
-                             array('User-Agent: Laconica/' . LACONICA_VERSION));
-
-    if ($result->status == 403) { # not authorized, don't send again
-        common_debug('403 result, deleting subscription', __FILE__);
-        # FIXME: figure out how to delete this
-        # $subscription->delete();
-        return false;
-    } else if ($result->status != 200) {
-        common_debug('Error status '.$result->status, __FILE__);
-        return false;
-    } else { # success!
-        parse_str($result->body, $return);
-        if ($return['omb_version'] == OMB_VERSION_01) {
-            return true;
-        } else {
-            return false;
-        }
-    }
+    return;
 }
 
-function omb_broadcast_profile($profile)
-{
-    # First, get remote users subscribed to this profile
-    # XXX: use a join here rather than looping through results
-    $sub = new Subscription();
-    $sub->subscribed = $profile->id;
-    if ($sub->find()) {
-        $updated = array();
-        while ($sub->fetch()) {
-            $rp = Remote_profile::staticGet('id', $sub->subscriber);
-            if ($rp) {
-                if (!array_key_exists($rp->updateprofileurl, $updated)) {
-                    if (omb_update_profile($profile, $rp, $sub)) {
-                        $updated[$rp->updateprofileurl] = true;
-                    }
-                }
-            }
-        }
+class Laconica_OMB_Service_Consumer extends OMB_Service_Consumer {
+    public function __construct($urls)
+    {
+        $this->services       = $urls;
+        $this->datastore      = omb_oauth_datastore();
+        $this->oauth_consumer = omb_oauth_consumer();
+        $this->fetcher        = Auth_Yadis_Yadis::getHTTPFetcher();
     }
+
 }
 
-function omb_update_profile($profile, $remote_profile, $subscription)
+function profile_to_omb_profile($uri, $profile, $force = false)
 {
-    $user = User::staticGet($profile->id);
-    $con = omb_oauth_consumer();
-    $token = new OAuthToken($subscription->token, $subscription->secret);
-    $url = $remote_profile->updateprofileurl;
-    $parsed = parse_url($url);
-    $params = array();
-    parse_str($parsed['query'], $params);
-    $req = OAuthRequest::from_consumer_and_token($con, $token,
-                                                 "POST", $url, $params);
-    $req->set_parameter('omb_version', OMB_VERSION_01);
-    $req->set_parameter('omb_listenee', $user->uri);
-    $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname));
-    $req->set_parameter('omb_listenee_nickname', $profile->nickname);
-
-    # We use blanks to force emptying any existing values in these optional fields
-
-    $req->set_parameter('omb_listenee_fullname',
-                        ($profile->fullname) ? $profile->fullname : '');
-    $req->set_parameter('omb_listenee_homepage',
-                        ($profile->homepage) ? $profile->homepage : '');
-    $req->set_parameter('omb_listenee_bio',
-                        ($profile->bio) ? $profile->bio : '');
-    $req->set_parameter('omb_listenee_location',
-                        ($profile->location) ? $profile->location : '');
+    $omb_profile = new OMB_Profile($uri);
+    $omb_profile->setNickname($profile->nickname);
+    $omb_profile->setLicenseURL(common_config('license', 'url'));
+    if (!is_null($profile->fullname)) {
+        $omb_profile->setFullname($profile->fullname);
+    } elseif ($force) {
+        $omb_profile->setFullname('');
+    }
+    if (!is_null($profile->homepage)) {
+        $omb_profile->setHomepage($profile->homepage);
+    } elseif ($force) {
+        $omb_profile->setHomepage('');
+    }
+    if (!is_null($profile->bio)) {
+        $omb_profile->setBio($profile->bio);
+    } elseif ($force) {
+        $omb_profile->setBio('');
+    }
+    if (!is_null($profile->location)) {
+        $omb_profile->setLocation($profile->location);
+    } elseif ($force) {
+        $omb_profile->setLocation('');
+    }
+    if (!is_null($profile->profileurl)) {
+        $omb_profile->setProfileURL($profile->profileurl);
+    } elseif ($force) {
+        $omb_profile->setProfileURL('');
+    }
 
     $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
-    $req->set_parameter('omb_listenee_avatar',
-                        ($avatar) ? $avatar->url : '');
+    if ($avatar) {
+        $omb_profile->setAvatarURL($avatar->url);
+    } elseif ($force) {
+        $omb_profile->setAvatarURL('');
+    }
+    return $omb_profile;
+}
 
-    $req->sign_request(omb_hmac_sha1(), $con, $token);
+function notice_to_omb_notice($notice)
+{
+    /* Create an OMB_Notice for $notice. */
+    $user = User::staticGet('id', $notice->profile_id);
 
-    # We re-use this tool's fetcher, since it's pretty good
+    if (!$user) {
+        return null;
+    }
 
-    $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+    $profile = $user->getProfile();
 
-    $result = $fetcher->post($req->get_normalized_http_url(),
-                             $req->to_postdata(),
-                             array('User-Agent: Laconica/' . LACONICA_VERSION));
+    $omb_notice = new OMB_Notice(profile_to_omb_profile($user->uri, $profile),
+                                 $notice->uri,
+                                 $notice->content);
+    $omb_notice->setURL(common_local_url('shownotice', array('notice' =>
+                                                                 $notice->id)));
+    $omb_notice->setLicenseURL(common_config('license', 'url'));
 
-    if (empty($result) || !$result) {
-        common_debug("Unable to contact " . $req->get_normalized_http_url());
-    } else if ($result->status == 403) { # not authorized, don't send again
-        common_debug('403 result, deleting subscription', __FILE__);
-        $subscription->delete();
-        return false;
-    } else if ($result->status != 200) {
-        common_debug('Error status '.$result->status, __FILE__);
-        return false;
-    } else { # success!
-        parse_str($result->body, $return);
-        if (isset($return['omb_version']) && $return['omb_version'] === OMB_VERSION_01) {
-            return true;
-        } else {
-            return false;
-        }
-    }
+    return $omb_notice;
 }
+?>
index 51546107255730b7cf7a661ef8cbfb59c3b5152c..34c7188b2d6eba6013439595216601f29a094316 100644 (file)
@@ -39,7 +39,7 @@ class UnQueueManager
          case 'omb':
             if ($this->_isLocal($notice)) {
                 require_once(INSTALLDIR.'/lib/omb.php');
-                omb_broadcast_remote_subscribers($notice);
+                omb_broadcast_notice($notice);
             }
             break;
          case 'public':
index 1587192b6fb5b9c1532e6af18411fa76dbc44ba4..cc5263ae23e8aeb20ea763e65b5fb33d33925e9f 100755 (executable)
@@ -57,7 +57,7 @@ class OmbQueueHandler extends QueueHandler
             $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
             return true;
         } else {
-            return omb_broadcast_remote_subscribers($notice);
+            return omb_broadcast_notice($notice);
         }
     }