Merge branch '0.9.x' of git@gitorious.org:laconica/mainline into 0.9.x

This commit is contained in:
Evan Prodromou 2009-08-20 17:13:40 -04:00
commit 4b2aa51750
32 changed files with 3336 additions and 1399 deletions

View 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());
}
}
}
?>

View 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.
*
@ -15,285 +26,116 @@
*
* 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);
}
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/
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/omb.php');
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;
}
/* Restore session data. RemotesubscribeAction should have stored
this entry. */
$service = unserialize($_SESSION['oauth_authorization_request']);
$omb = $_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_debug('stored request: '. print_r($service, true), __FILE__);
common_remove_magic_from_request();
$req = OAuthRequest::from_request('POST', common_local_url('finishuserauthorization'));
$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.'));
/* 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 {
$this->clientError(_('Couldn\'t convert request token to ' .
'access token.'));
return;
}
} 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;
}
# XXX: possible attack point; subscribe and return someone else's profile URI
/* The service URLs are not accessible from datastore, so setting them
after insertion of the profile. */
$remote = Remote_profile::staticGet('uri', $service->getListenerURI());
$remote = Remote_profile::staticGet('uri', $omb['listener']);
$orig_remote = clone($remote);
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();
}
$remote->postnoticeurl =
$service->getServiceURI(OMB_ENDPOINT_POSTNOTICE);
$remote->updateprofileurl =
$service->getServiceURI(OMB_ENDPOINT_UPDATEPROFILE);
$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)) {
if (!$remote->update($orig_remote)) {
$this->serverError(_('Error updating remote profile'));
return;
}
} else {
$remote->created = DB_DataObject_Cast::dateTime(); # current time
if (!$remote->insert()) {
$this->serverError(_('Error inserting remote profile'));
return;
}
}
if ($user->hasBlocked($profile)) {
$this->clientError(_('That user has blocked you from subscribing.'));
return;
}
$sub = new Subscription();
$sub->subscriber = $remote->id;
$sub->subscribed = $user->id;
$sub_exists = false;
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;
}
# 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']);
}
}

View 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.
*
@ -17,75 +28,49 @@
* 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;
}
}
?>

View 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.
*
@ -15,12 +26,27 @@
*
* 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);
}
require_once INSTALLDIR.'/lib/omb.php';
require_once INSTALLDIR.'/extlib/libomb/service_consumer.php';
require_once INSTALLDIR.'/extlib/libomb/profile.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/
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/lib/omb.php');
class RemotesubscribeAction extends Action
{
var $nickname;
@ -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).'));
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;
}
# 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'))
{
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;
}
if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) {
$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;
$_SESSION['oauth_authorization_request'] = serialize($service);
# Redirect to authorization service
common_redirect($req->to_url(), 303);
return;
/* Redirect to the remote service for authorization. */
common_redirect($target_url, 303);
}
}
?>

View 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());
}
}
}
?>

View File

@ -1,5 +1,16 @@
<?php
/*
/**
* Unsubscribe handler
*
* 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.
*
@ -17,6 +28,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) {
exit(1);
}
/**
* Unsubscribe handler
*
* @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 UnsubscribeAction extends Action
{
@ -31,16 +56,18 @@ class UnsubscribeAction extends Action
$user = common_current_user();
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)));
return;
}
# CSRF protection
/* Use a session token for CSRF protection. */
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. Try again, please.'));
$this->clientError(_('There was a problem with your session token. ' .
'Try again, please.'));
return;
}
@ -53,7 +80,7 @@ class UnsubscribeAction extends Action
$other = Profile::staticGet('id', $other_id);
if (!$other_id) {
if (!$other) {
$this->clientError(_('No profile with that id.'));
return;
}
@ -76,8 +103,8 @@ class UnsubscribeAction extends Action
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subscriptions', array('nickname' =>
$user->nickname)),
common_redirect(common_local_url('subscriptions',
array('nickname' => $user->nickname)),
303);
}
}

View 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.
*
@ -17,167 +28,37 @@
* 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;
}
}
}
?>

View 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.
*
@ -17,9 +28,13 @@
* 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,218 +221,56 @@ 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);
}
$accepted = $this->arg('accept');
try {
list($val, $token) = $srv->continueUserAuth($accepted);
} catch (Exception $e) {
$this->clientError($e->getMessage());
return;
}
if ($val !== false) {
common_redirect($val, 303);
} elseif ($accepted) {
$this->showAcceptMessage($token);
} else {
if (!$callback) {
$this->showRejectMessage();
} else {
# XXX: not 100% sure how to signal failure... just redirect without token?
common_redirect($callback, 303);
}
$this->showRejectMessage();
}
}
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;
}
if ($exists) {
if (!$remote->update($orig_remote)) {
return false;
}
} 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;
}
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)
{
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_profile_url($nickname)) {
throw new Exception("Profile URL '$profile' is for a local user.");
}
if ($profile == common_local_url('showstream', array('nickname' => $nickname))) {
throw new OAuthException("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.");
}
}
}
?>

View 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'));
$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');
// 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();
}
/**
* 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)
{
$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');
$srv = new OMB_Service_Provider(profile_to_omb_profile($user->uri,
$user->getProfile()));
/* Use libombs default XRDS Writer. */
$xrds_writer = null;
$srv->writeXRDS(new Laconica_XRDS_Mapper(), $xrds_writer);
}
}
class Laconica_XRDS_Mapper implements OMB_XRDS_Mapper
{
protected $urls;
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');
}
public function getURL($action)
{
return common_local_url($this->urls[$action]);
}
}
?>

View File

@ -115,7 +115,7 @@ class Design extends Memcached_DataObject
return new WebColor($color);
} catch (WebColorException $e) {
// This shouldn't happen
common_log(LOG_ERR, "Unable to create color for design $id.",
common_log(LOG_ERR, "Unable to create web color for $color",
__FILE__);
return null;
}

2
db/08to09_pg.sql Normal file
View File

@ -0,0 +1,2 @@
// SQL commands to update an 0.8.x version of Laconica
// to 0.9.x.

View File

@ -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];
}
}
?>

View File

@ -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');
?>

198
extlib/libomb/datastore.php Executable file
View File

@ -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 libombs main interface to the applications data.
*
* It is the users 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 its 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 users 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();
}
}
?>

99
extlib/libomb/helper.php Normal file
View File

@ -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);
}
}
}
?>

View File

@ -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");
}
}
?>

View File

@ -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 {
}
?>

272
extlib/libomb/notice.php Executable file
View File

@ -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;
}
}
?>

196
extlib/libomb/omb_yadis_xrds.php Executable file
View File

@ -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;
}
}

View File

@ -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();
}
}
?>

317
extlib/libomb/profile.php Executable file
View File

@ -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;
}
}
?>

View File

@ -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);
}
}
?>

View File

@ -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 services 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 users URI
*
* Returns the URI of the remote user, i. e. the listener.
*
* @access public
*
* @return string The remote users URI
**/
public function getRemoteUserURI() {
return $this->listener_uri;
}
/**
* Get the listenees URI
*
* Returns the URI of the user being subscribed to, i. e. the local user.
*
* @access public
*
* @return string The local users 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 subscribers 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) {
/* Thats 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 listeners 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, its 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);
}
}
}

View File

@ -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 dont 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;
}
}

View File

@ -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 {
}
?>

33
extlib/libomb/xrds_mapper.php Executable file
View File

@ -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);
}
?>

33
extlib/libomb/xrds_writer.php Executable file
View File

@ -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);
}
?>

View File

@ -180,6 +180,9 @@ function handlePost()
$password = $_POST['password'];
$sitename = $_POST['sitename'];
$fancy = !empty($_POST['fancy']);
$server = $_SERVER['HTTP_HOST'];
$path = substr(dirname($_SERVER['PHP_SELF']), 1);
?>
<dl class="system_notice">
<dt>Page notice</dt>
@ -219,20 +222,42 @@ function handlePost()
}
switch($dbtype) {
case 'mysql': mysql_db_installer($host, $database, $username, $password, $sitename, $fancy);
break;
case 'pgsql': pgsql_db_installer($host, $database, $username, $password, $sitename, $fancy);
break;
default:
case 'mysql':
$db = mysql_db_installer($host, $database, $username, $password);
break;
case 'pgsql':
$db = pgsql_db_installer($host, $database, $username, $password);
break;
default:
}
if ($path) $path .= '/';
updateStatus("You can visit your <a href='/$path'>new Laconica site</a>.");
if (!$db) {
// database connection failed, do not move on to create config file.
return false;
}
updateStatus("Writing config file...");
$res = writeConf($sitename, $server, $path, $fancy, $db);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
/*
TODO https needs to be considered
*/
$link = "http://".$server.'/'.$path;
updateStatus("Laconica has been installed at $link");
updateStatus("You can visit your <a href='$link'>new Laconica site</a>.");
?>
<?php
}
function pgsql_db_installer($host, $database, $username, $password, $sitename, $fancy) {
function pgsql_db_installer($host, $database, $username, $password) {
$connstring = "dbname=$database host=$host user=$username";
//No password would mean trust authentication used.
@ -265,7 +290,7 @@ function pgsql_db_installer($host, $database, $username, $password, $sitename, $
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
@ -276,29 +301,24 @@ function pgsql_db_installer($host, $database, $username, $password, $sitename, $
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return;
return false;
}
}
pg_query($conn, 'COMMIT');
updateStatus("Writing config file...");
if (empty($password)) {
$sqlUrl = "pgsql://$username@$host/$database";
}
else {
$sqlUrl = "pgsql://$username:$password@$host/$database";
}
$res = writeConf($sitename, $sqlUrl, $fancy, 'pgsql');
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
$db = array('type' => 'pgsql', 'database' => $sqlUrl);
return $db;
}
function mysql_db_installer($host, $database, $username, $password, $sitename, $fancy) {
function mysql_db_installer($host, $database, $username, $password) {
updateStatus("Starting installation...");
updateStatus("Checking database...");
@ -306,21 +326,21 @@ function mysql_db_installer($host, $database, $username, $password, $sitename, $
if (!$conn) {
updateStatus("Can't connect to server '$host' as '$username'.", true);
showForm();
return;
return false;
}
updateStatus("Changing to database...");
$res = mysql_select_db($database, $conn);
if (!$res) {
updateStatus("Can't change to database.", true);
showForm();
return;
return false;
}
updateStatus("Running database script...");
$res = runDbScript(INSTALLDIR.'/db/laconica.sql', $conn);
if ($res === false) {
updateStatus("Can't run database script.", true);
showForm();
return;
return false;
}
foreach (array('sms_carrier' => 'SMS carrier',
'notice_source' => 'notice source',
@ -331,35 +351,44 @@ function mysql_db_installer($host, $database, $username, $password, $sitename, $
if ($res === false) {
updateStatus(sprintf("Can't run %d script.", $name), true);
showForm();
return;
return false;
}
}
updateStatus("Writing config file...");
$sqlUrl = "mysqli://$username:$password@$host/$database";
$res = writeConf($sitename, $sqlUrl, $fancy);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
}
function writeConf($sitename, $sqlUrl, $fancy, $type='mysql')
$db = array('type' => 'mysql', 'database' => $sqlUrl);
return $db;
}
function writeConf($sitename, $server, $path, $fancy, $db)
{
$res = file_put_contents(INSTALLDIR.'/config.php',
"<?php\n".
"if (!defined('LACONICA')) { exit(1); }\n\n".
"\$config['site']['name'] = \"$sitename\";\n\n".
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
"\$config['db']['database'] = \"$sqlUrl\";\n\n".
($type == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n" .
"\$config['db']['type'] = \"$type\";\n\n" : '').
"?>");
// assemble configuration file in a string
$cfg = "<?php\n".
"if (!defined('LACONICA')) { exit(1); }\n\n".
// site name
"\$config['site']['name'] = '$sitename';\n\n".
// site location
"\$config['site']['server'] = '$server';\n".
"\$config['site']['path'] = '$path'; \n\n".
// checks if fancy URLs are enabled
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
// database
"\$config['db']['database'] = '{$db['database']}';\n\n".
($type == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
"\$config['db']['type'] = '{$db['type']}';\n\n".
"?>";
// write configuration file out to install directory
$res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
return $res;
}
function runDbScript($filename, $conn, $type='mysql')
function runDbScript($filename, $conn, $type = 'mysql')
{
$sql = trim(file_get_contents($filename));
$stmts = explode(';', $sql);
@ -368,10 +397,15 @@ function runDbScript($filename, $conn, $type='mysql')
if (!mb_strlen($stmt)) {
continue;
}
if ($type == 'mysql') {
$res = mysql_query($stmt, $conn);
} elseif ($type=='pgsql') {
$res = pg_query($conn, $stmt);
switch ($type) {
case 'mysql':
$res = mysql_query($stmt, $conn);
break;
case 'pgsql':
$res = pg_query($conn, $stmt);
break;
default:
updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
}
if ($res === false) {
updateStatus("FAILED SQL: $stmt");

View File

@ -17,15 +17,16 @@
* 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 users 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_Providers 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));
}
}
}
?>

View File

@ -17,36 +17,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
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');
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)
{
return $xrd->services(array(omb_service_filter($type)));
}
function omb_service_filter($type)
{
return create_function('$s',
'return omb_match_service($s, \''.$type.'\');');
}
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)
function omb_broadcast_notice($notice)
{
# First, get remote users subscribed to this profile
$omb_notice = notice_to_omb_notice($notice);
/* 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;
}
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;
common_debug('Finished to ' . $rp->postnoticeurl, __FILE__);
}
$rp->free();
unset($rp);
return true;
return;
}
function omb_post_notice($notice, $remote_profile, $subscription)
function omb_broadcast_profile($profile)
{
return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret);
}
function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
{
$user = User::staticGet('id', $notice->profile_id);
$user = User::staticGet('id', $profile->id);
if (!$user) {
return false;
}
$con = omb_oauth_consumer();
$profile = $user->getProfile();
$token = new OAuthToken($tk, $secret);
$omb_profile = profile_to_omb_profile($user->uri, $profile, true);
$url = $postnoticeurl;
$parsed = parse_url($url);
$params = array();
parse_str($parsed['query'], $params);
/* Get remote users subscribed to this profile. */
$rp = new Remote_profile();
$req = OAuthRequest::from_consumer_and_token($con, $token,
'POST', $url, $params);
$rp->query('SELECT updateprofileurl, token, secret ' .
'FROM subscription JOIN remote_profile ' .
'ON subscription.subscriber = remote_profile.id ' .
'WHERE subscription.subscribed = ' . $profile->id . ' ');
$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'));
$posted = array();
$user->free();
unset($user);
$req->sign_request(omb_hmac_sha1(), $con, $token);
# We re-use this tool's fetcher, since it's pretty good
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
if (!$fetcher) {
common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__);
return false;
}
$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;
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;
common_debug('Finished to ' . $rp->updateprofileurl, __FILE__);
}
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 : '');
$req->sign_request(omb_hmac_sha1(), $con, $token);
# 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 (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;
}
if ($avatar) {
$omb_profile->setAvatarURL($avatar->url);
} elseif ($force) {
$omb_profile->setAvatarURL('');
}
return $omb_profile;
}
function notice_to_omb_notice($notice)
{
/* Create an OMB_Notice for $notice. */
$user = User::staticGet('id', $notice->profile_id);
if (!$user) {
return null;
}
$profile = $user->getProfile();
$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'));
return $omb_notice;
}
?>

View 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':

View File

@ -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);
}
}