2009-08-10 13:48:50 +01:00
|
|
|
|
<?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.
|
|
|
|
|
*
|
2009-08-21 11:13:24 +01:00
|
|
|
|
* The OMB_Profile passed to the constructor of OMB_Service_Provider should
|
|
|
|
|
* not represent the user specified in the authorization request, but the one
|
|
|
|
|
* currently logged in to the service. This condition being satisfied,
|
|
|
|
|
* handleUserAuth will check whether the listener specified in the request is
|
|
|
|
|
* identical to the logged in user.
|
|
|
|
|
*
|
2009-08-10 13:48:50 +01:00
|
|
|
|
* @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'];
|
2009-08-21 11:13:24 +01:00
|
|
|
|
if (!OMB_Helper::validateURL($this->callback)) {
|
|
|
|
|
throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
|
|
|
|
|
'Invalid callback URL specified');
|
|
|
|
|
}
|
2009-08-10 13:48:50 +01:00
|
|
|
|
}
|
|
|
|
|
$this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
|
|
|
|
|
|
|
|
|
|
return $this->remote_user;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Continue the OAuth dance after user authorization
|
|
|
|
|
*
|
|
|
|
|
* Performs the appropriate actions after user answered the authorization
|
|
|
|
|
* request.
|
|
|
|
|
*
|
|
|
|
|
* @param bool $accepted Whether the user granted authorization
|
|
|
|
|
*
|
|
|
|
|
* @access public
|
|
|
|
|
*
|
|
|
|
|
* @return array A two-component array with the values:
|
|
|
|
|
* - callback The callback URL or null if none given
|
|
|
|
|
* - token The authorized request token or null if not
|
|
|
|
|
* authorized.
|
|
|
|
|
**/
|
|
|
|
|
public function continueUserAuth($accepted) {
|
|
|
|
|
$callback = $this->callback;
|
|
|
|
|
if (!$accepted) {
|
|
|
|
|
$this->datastore->revoke_token($this->token->key);
|
|
|
|
|
$this->token = null;
|
|
|
|
|
/* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way
|
|
|
|
|
laconica works. Moreover I don’t know the right way either. */
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
$this->datastore->authorize_token($this->token->key);
|
|
|
|
|
$this->datastore->saveProfile($this->remote_user);
|
|
|
|
|
$this->datastore->saveSubscription($this->user->getIdentifierURI(),
|
|
|
|
|
$this->remote_user->getIdentifierURI(), $this->token);
|
|
|
|
|
|
|
|
|
|
if (!is_null($this->callback)) {
|
|
|
|
|
/* Callback wants to get some informations as well. */
|
|
|
|
|
$params = $this->user->asParameters('omb_listener', false);
|
|
|
|
|
|
|
|
|
|
$params['oauth_token'] = $this->token->key;
|
|
|
|
|
$params['omb_version'] = OMB_VERSION;
|
|
|
|
|
|
|
|
|
|
$callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
|
|
|
|
|
foreach ($params as $k => $v) {
|
|
|
|
|
$callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
|
|
|
|
|
OAuthUtil::urlencode_rfc3986($v) . '&';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return array($callback, $this->token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Echo an access token
|
|
|
|
|
*
|
2009-08-21 11:13:24 +01:00
|
|
|
|
* Outputs an access token for the query found in $_POST. OMB 0.1 specifies
|
|
|
|
|
* that the access token request has to be a POST even if OAuth allows GET as
|
|
|
|
|
* well.
|
2009-08-10 13:48:50 +01:00
|
|
|
|
*
|
|
|
|
|
* @access public
|
|
|
|
|
**/
|
|
|
|
|
public function writeAccessToken() {
|
|
|
|
|
OMB_Helper::removeMagicQuotesFromRequest();
|
2009-08-21 11:13:24 +01:00
|
|
|
|
echo $this->getOAuthServer()->fetch_access_token(
|
|
|
|
|
OAuthRequest::from_request('POST'));
|
2009-08-10 13:48:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*
|
2009-08-21 11:13:24 +01:00
|
|
|
|
* Handles a postnotice request posted to this service. Saves the notice
|
|
|
|
|
* through the OMB_Datastore.
|
2009-08-10 13:48:50 +01:00
|
|
|
|
*
|
|
|
|
|
* @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();
|
2009-08-21 11:13:24 +01:00
|
|
|
|
$req = OAuthRequest::from_request('POST');
|
2009-08-10 13:48:50 +01:00
|
|
|
|
$listenee = $req->get_parameter('omb_listenee');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
|
|
|
|
|
} catch (OAuthException $e) {
|
|
|
|
|
header('HTTP/1.1 403 Forbidden');
|
2010-02-15 23:19:16 +00:00
|
|
|
|
// @debug hack
|
|
|
|
|
throw OMB_RemoteServiceException::forRequest($uri,
|
|
|
|
|
'Revoked accesstoken for ' . $listenee . ': ' . $e->getMessage());
|
|
|
|
|
// @end debug
|
2009-08-10 13:48:50 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|