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