forked from GNUsocial/gnu-social
431 lines
14 KiB
PHP
431 lines
14 KiB
PHP
|
<?php
|
|||
|
|
|||
|
require_once 'constants.php';
|
|||
|
require_once 'Validate.php';
|
|||
|
require_once 'Auth/Yadis/Yadis.php';
|
|||
|
require_once 'OAuth.php';
|
|||
|
require_once 'unsupportedserviceexception.php';
|
|||
|
require_once 'remoteserviceexception.php';
|
|||
|
require_once 'omb_yadis_xrds.php';
|
|||
|
require_once 'helper.php';
|
|||
|
|
|||
|
/**
|
|||
|
* OMB service representation
|
|||
|
*
|
|||
|
* This class represents a complete remote OMB service. It provides discovery
|
|||
|
* and execution of the service’s methods.
|
|||
|
*
|
|||
|
* PHP version 5
|
|||
|
*
|
|||
|
* LICENSE: This program is free software: you can redistribute it and/or modify
|
|||
|
* it under the terms of the GNU Affero General Public License as published by
|
|||
|
* the Free Software Foundation, either version 3 of the License, or
|
|||
|
* (at your option) any later version.
|
|||
|
*
|
|||
|
* This program is distributed in the hope that it will be useful,
|
|||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
* GNU Affero General Public License for more details.
|
|||
|
*
|
|||
|
* You should have received a copy of the GNU Affero General Public License
|
|||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
*
|
|||
|
* @package OMB
|
|||
|
* @author Adrian Lang <mail@adrianlang.de>
|
|||
|
* @copyright 2009 Adrian Lang
|
|||
|
* @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
|
|||
|
**/
|
|||
|
|
|||
|
class OMB_Service_Consumer {
|
|||
|
protected $url; /* The service URL */
|
|||
|
protected $services; /* An array of strings mapping service URI to
|
|||
|
service URL */
|
|||
|
|
|||
|
protected $token; /* An OAuthToken */
|
|||
|
|
|||
|
protected $listener_uri; /* The URI identifying the listener, i. e. the
|
|||
|
remote user. */
|
|||
|
|
|||
|
protected $listenee_uri; /* The URI identifying the listenee, i. e. the
|
|||
|
local user during an auth request. */
|
|||
|
|
|||
|
/**
|
|||
|
* According to OAuth Core 1.0, an user authorization request is no full-blown
|
|||
|
* OAuth request. nonce, timestamp, consumer_key and signature are not needed
|
|||
|
* in this step. See http://laconi.ca/trac/ticket/827 for more informations.
|
|||
|
*
|
|||
|
* Since Laconica up to version 0.7.2 performs a full OAuth request check, a
|
|||
|
* correct request would fail.
|
|||
|
**/
|
|||
|
public $performLegacyAuthRequest = true;
|
|||
|
|
|||
|
/* Helper stuff we are going to need. */
|
|||
|
protected $fetcher;
|
|||
|
protected $oauth_consumer;
|
|||
|
protected $datastore;
|
|||
|
|
|||
|
/**
|
|||
|
* Constructor for OMB_Service_Consumer
|
|||
|
*
|
|||
|
* Initializes an OMB_Service_Consumer object representing the OMB service
|
|||
|
* specified by $service_url. Performs a complete service discovery using
|
|||
|
* Yadis.
|
|||
|
* Throws OMB_UnsupportedServiceException if XRDS file does not specify a
|
|||
|
* complete OMB service.
|
|||
|
*
|
|||
|
* @param string $service_url The URL of the service
|
|||
|
* @param string $consumer_url An URL representing the consumer
|
|||
|
* @param OMB_Datastore $datastore An instance of a class implementing
|
|||
|
* OMB_Datastore
|
|||
|
*
|
|||
|
* @access public
|
|||
|
**/
|
|||
|
public function __construct ($service_url, $consumer_url, $datastore) {
|
|||
|
$this->url = $service_url;
|
|||
|
$this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
|
|||
|
$this->datastore = $datastore;
|
|||
|
$this->oauth_consumer = new OAuthConsumer($consumer_url, '');
|
|||
|
|
|||
|
$xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
|
|||
|
|
|||
|
/* Detect our services. This performs a validation as well, since
|
|||
|
getService und getXRD throw exceptions on failure. */
|
|||
|
$this->services = array();
|
|||
|
|
|||
|
foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
|
|||
|
OMB_VERSION => OMB_Helper::$OMB_SERVICES)
|
|||
|
as $service_root => $targetservices) {
|
|||
|
$uris = $xrds->getService($service_root)->getURIs();
|
|||
|
$xrd = $xrds->getXRD($uris[0]);
|
|||
|
foreach ($targetservices as $targetservice) {
|
|||
|
$yadis_service = $xrd->getService($targetservice);
|
|||
|
if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
|
|||
|
$localid = $yadis_service->getElements('xrd:LocalID');
|
|||
|
$this->listener_uri = $yadis_service->parser->content($localid[0]);
|
|||
|
}
|
|||
|
$uris = $yadis_service->getURIs();
|
|||
|
$this->services[$targetservice] = $uris[0];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the handler URI for a service
|
|||
|
*
|
|||
|
* Returns the URI the remote web service has specified for the given
|
|||
|
* service.
|
|||
|
*
|
|||
|
* @param string $service The URI identifying the service
|
|||
|
*
|
|||
|
* @access public
|
|||
|
*
|
|||
|
* @return string The service handler URI
|
|||
|
**/
|
|||
|
public function getServiceURI($service) {
|
|||
|
return $this->services[$service];
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the remote user’s URI
|
|||
|
*
|
|||
|
* Returns the URI of the remote user, i. e. the listener.
|
|||
|
*
|
|||
|
* @access public
|
|||
|
*
|
|||
|
* @return string The remote user’s URI
|
|||
|
**/
|
|||
|
public function getRemoteUserURI() {
|
|||
|
return $this->listener_uri;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the listenee’s URI
|
|||
|
*
|
|||
|
* Returns the URI of the user being subscribed to, i. e. the local user.
|
|||
|
*
|
|||
|
* @access public
|
|||
|
*
|
|||
|
* @return string The local user’s URI
|
|||
|
**/
|
|||
|
public function getListeneeURI() {
|
|||
|
return $this->listenee_uri;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Request a request token
|
|||
|
*
|
|||
|
* Performs a token request on the service. Returns an OAuthToken on success.
|
|||
|
* Throws an exception if the request fails.
|
|||
|
*
|
|||
|
* @access public
|
|||
|
*
|
|||
|
* @return OAuthToken An unauthorized request token
|
|||
|
**/
|
|||
|
public function requestToken() {
|
|||
|
/* Set the token to null just in case the user called setToken. */
|
|||
|
$this->token = null;
|
|||
|
|
|||
|
$result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
|
|||
|
array('omb_listener' => $this->listener_uri));
|
|||
|
if ($result->status != 200) {
|
|||
|
throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
|
|||
|
$result);
|
|||
|
}
|
|||
|
parse_str($result->body, $return);
|
|||
|
if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
|
|||
|
throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
|
|||
|
$result);
|
|||
|
}
|
|||
|
$this->setToken($return['oauth_token'], $return['oauth_token_secret']);
|
|||
|
return $this->token;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
*
|
|||
|
* Request authorization
|
|||
|
*
|
|||
|
* Returns an URL which equals to an authorization request. The end user
|
|||
|
* should be redirected to this location to perform authorization.
|
|||
|
* The $finish_url should be a local resource which invokes
|
|||
|
* OMB_Consumer::finishAuthorization on request.
|
|||
|
*
|
|||
|
* @param OMB_Profile $profile An OMB_Profile object representing the
|
|||
|
* soon-to-be subscribed (i. e. local) user
|
|||
|
* @param string $finish_url Target location after successful
|
|||
|
* authorization
|
|||
|
*
|
|||
|
* @access public
|
|||
|
*
|
|||
|
* @return string An URL representing an authorization request
|
|||
|
**/
|
|||
|
public function requestAuthorization($profile, $finish_url) {
|
|||
|
if ($this->performLegacyAuthRequest) {
|
|||
|
$params = $profile->asParameters('omb_listenee', false);
|
|||
|
$params['omb_listener'] = $this->listener_uri;
|
|||
|
$params['oauth_callback'] = $finish_url;
|
|||
|
|
|||
|
$url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
|
|||
|
} else {
|
|||
|
|
|||
|
$params = array(
|
|||
|
'oauth_callback' => $finish_url,
|
|||
|
'oauth_token' => $this->token->key,
|
|||
|
'omb_version' => OMB_VERSION,
|
|||
|
'omb_listener' => $this->listener_uri);
|
|||
|
|
|||
|
$params = array_merge($profile->asParameters('omb_listenee', false). $params);
|
|||
|
|
|||
|
/* Build result URL. */
|
|||
|
$url = $this->services[OAUTH_ENDPOINT_AUTHORIZE];
|
|||
|
$url .= (strrpos($url, '?') === false ? '?' : '&');
|
|||
|
foreach ($params as $k => $v) {
|
|||
|
$url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->listenee_uri = $profile->getIdentifierURI();
|
|||
|
|
|||
|
return $url;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Finish authorization
|
|||
|
*
|
|||
|
* Finish the subscription process by converting the received and authorized
|
|||
|
* request token into an access token. After that, the subscriber’s profile
|
|||
|
* and the subscription are stored in the database.
|
|||
|
* Expects an OAuthRequest in query parameters.
|
|||
|
* Throws exceptions on failure.
|
|||
|
*
|
|||
|
* @access public
|
|||
|
**/
|
|||
|
public function finishAuthorization() {
|
|||
|
OMB_Helper::removeMagicQuotesFromRequest();
|
|||
|
$req = OAuthRequest::from_request();
|
|||
|
if ($req->get_parameter('oauth_token') !=
|
|||
|
$this->token->key) {
|
|||
|
/* That’s not the token I wanted to get authorized. */
|
|||
|
throw new OAuthException('The authorized token does not equal the ' .
|
|||
|
'submitted token.');
|
|||
|
}
|
|||
|
|
|||
|
if ($req->get_parameter('omb_version') != OMB_VERSION) {
|
|||
|
throw new OMB_RemoteServiceException('The remote service uses an ' .
|
|||
|
'unsupported OMB version');
|
|||
|
}
|
|||
|
|
|||
|
/* Construct the profile to validate it. */
|
|||
|
|
|||
|
/* Fix OMB bug. Listener URI is not passed. */
|
|||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
|||
|
$params = $_POST;
|
|||
|
} else {
|
|||
|
$params = $_GET;
|
|||
|
}
|
|||
|
$params['omb_listener'] = $this->listener_uri;
|
|||
|
|
|||
|
require_once 'profile.php';
|
|||
|
$listener = OMB_Profile::fromParameters($params, 'omb_listener');
|
|||
|
|
|||
|
/* Ask the remote service to convert the authorized request token into an
|
|||
|
access token. */
|
|||
|
|
|||
|
$result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
|
|||
|
if ($result->status != 200) {
|
|||
|
throw new OAuthException('Could not get access token');
|
|||
|
}
|
|||
|
|
|||
|
parse_str($result->body, $return);
|
|||
|
if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
|
|||
|
throw new OAuthException('Could not get access token');
|
|||
|
}
|
|||
|
$this->setToken($return['oauth_token'], $return['oauth_token_secret']);
|
|||
|
|
|||
|
/* Subscription is finished and valid. Now store the new subscriber and the
|
|||
|
subscription in the database. */
|
|||
|
|
|||
|
$this->datastore->saveProfile($listener);
|
|||
|
$this->datastore->saveSubscription($this->listener_uri,
|
|||
|
$this->listenee_uri,
|
|||
|
$this->token);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Return the URI identifying the listener
|
|||
|
*
|
|||
|
* Returns the URI for the OMB user who tries to subscribe or already has
|
|||
|
* subscribed our user. This method is a workaround for a serious OMB flaw:
|
|||
|
* The Listener URI is not passed in the finishauthorization call.
|
|||
|
*
|
|||
|
* @access public
|
|||
|
*
|
|||
|
* @return string the listener’s URI
|
|||
|
**/
|
|||
|
public function getListenerURI() {
|
|||
|
return $this->listener_uri;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Inform the service about a profile update
|
|||
|
*
|
|||
|
* Sends an updated profile to the service.
|
|||
|
*
|
|||
|
* @param OMB_Profile $profile The profile that has changed
|
|||
|
*
|
|||
|
* @access public
|
|||
|
**/
|
|||
|
public function updateProfile($profile) {
|
|||
|
$params = $profile->asParameters('omb_listenee', true);
|
|||
|
$this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Inform the service about a new notice
|
|||
|
*
|
|||
|
* Sends a notice to the service.
|
|||
|
*
|
|||
|
* @param OMB_Notice $notice The notice
|
|||
|
*
|
|||
|
* @access public
|
|||
|
**/
|
|||
|
public function postNotice($notice) {
|
|||
|
$params = $notice->asParameters();
|
|||
|
$params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
|
|||
|
$this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set the token member variable
|
|||
|
*
|
|||
|
* Initializes the token based on given token and secret token.
|
|||
|
*
|
|||
|
* @param string $token The token
|
|||
|
* @param string $secret The secret token
|
|||
|
*
|
|||
|
* @access public
|
|||
|
**/
|
|||
|
public function setToken($token, $secret) {
|
|||
|
$this->token = new OAuthToken($token, $secret);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Prepare an OAuthRequest object
|
|||
|
*
|
|||
|
* Creates an OAuthRequest object mapping the request specified by the
|
|||
|
* parameters.
|
|||
|
*
|
|||
|
* @param string $action_uri The URI specifying the target service
|
|||
|
* @param array $params Additional parameters for the service call
|
|||
|
* @param string $method The HTTP method used to call the service
|
|||
|
* ('POST' or 'GET', usually)
|
|||
|
*
|
|||
|
* @access protected
|
|||
|
*
|
|||
|
* @return OAuthRequest the prepared request
|
|||
|
**/
|
|||
|
protected function prepareAction($action_uri, $params, $method) {
|
|||
|
$url = $this->services[$action_uri];
|
|||
|
|
|||
|
$url_params = array();
|
|||
|
parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
|
|||
|
|
|||
|
/* Add OMB version. */
|
|||
|
$url_params['omb_version'] = OMB_VERSION;
|
|||
|
|
|||
|
/* Add user-defined parameters. */
|
|||
|
$url_params = array_merge($url_params, $params);
|
|||
|
|
|||
|
$req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
|
|||
|
$this->token, $method, $url, $url_params);
|
|||
|
|
|||
|
/* Sign the request. */
|
|||
|
$req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
|
|||
|
$this->oauth_consumer, $this->token);
|
|||
|
|
|||
|
return $req;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Perform a service call
|
|||
|
*
|
|||
|
* Creates an OAuthRequest object and execute the mapped call as POST request.
|
|||
|
*
|
|||
|
* @param string $action_uri The URI specifying the target service
|
|||
|
* @param array $params Additional parameters for the service call
|
|||
|
*
|
|||
|
* @access protected
|
|||
|
*
|
|||
|
* @return Auth_Yadis_HTTPResponse The POST request response
|
|||
|
**/
|
|||
|
protected function performAction($action_uri, $params) {
|
|||
|
$req = $this->prepareAction($action_uri, $params, 'POST');
|
|||
|
|
|||
|
/* Return result page. */
|
|||
|
return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Perform an OMB action
|
|||
|
*
|
|||
|
* Executes an OMB action – to date, it’s one of updateProfile or postNotice.
|
|||
|
*
|
|||
|
* @param string $action_uri The URI specifying the target service
|
|||
|
* @param array $params Additional parameters for the service call
|
|||
|
* @param string $listenee_uri The URI identifying the local user for whom
|
|||
|
* the action is performed
|
|||
|
*
|
|||
|
* @access protected
|
|||
|
**/
|
|||
|
protected function performOMBAction($action_uri, $params, $listenee_uri) {
|
|||
|
$result = $this->performAction($action_uri, $params);
|
|||
|
if ($result->status == 403) {
|
|||
|
/* The remote user unsubscribed us. */
|
|||
|
$this->datastore->deleteSubscription($this->listener_uri, $listenee_uri);
|
|||
|
} else if ($result->status != 200 ||
|
|||
|
strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
|
|||
|
/* The server signaled an error or sent an incorrect response. */
|
|||
|
throw OMB_RemoteServiceException::fromYadis($action_uri, $result);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|