3 require_once 'constants.php';
4 require_once 'Validate.php';
5 require_once 'Auth/Yadis/Yadis.php';
6 require_once 'OAuth.php';
7 require_once 'unsupportedserviceexception.php';
8 require_once 'remoteserviceexception.php';
9 require_once 'omb_yadis_xrds.php';
10 require_once 'helper.php';
13 * OMB service representation
15 * This class represents a complete remote OMB service. It provides discovery
16 * and execution of the service’s methods.
20 * LICENSE: This program is free software: you can redistribute it and/or modify
21 * it under the terms of the GNU Affero General Public License as published by
22 * the Free Software Foundation, either version 3 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU Affero General Public License for more details.
30 * You should have received a copy of the GNU Affero General Public License
31 * along with this program. If not, see <http://www.gnu.org/licenses/>.
34 * @author Adrian Lang <mail@adrianlang.de>
35 * @copyright 2009 Adrian Lang
36 * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
39 class OMB_Service_Consumer {
40 protected $url; /* The service URL */
41 protected $services; /* An array of strings mapping service URI to
44 protected $token; /* An OAuthToken */
46 protected $listener_uri; /* The URI identifying the listener, i. e. the
49 protected $listenee_uri; /* The URI identifying the listenee, i. e. the
50 local user during an auth request. */
53 * According to OAuth Core 1.0, an user authorization request is no full-blown
54 * OAuth request. nonce, timestamp, consumer_key and signature are not needed
55 * in this step. See http://laconi.ca/trac/ticket/827 for more informations.
57 * Since Laconica up to version 0.7.2 performs a full OAuth request check, a
58 * correct request would fail.
60 public $performLegacyAuthRequest = true;
62 /* Helper stuff we are going to need. */
64 protected $oauth_consumer;
68 * Constructor for OMB_Service_Consumer
70 * Initializes an OMB_Service_Consumer object representing the OMB service
71 * specified by $service_url. Performs a complete service discovery using
73 * Throws OMB_UnsupportedServiceException if XRDS file does not specify a
74 * complete OMB service.
76 * @param string $service_url The URL of the service
77 * @param string $consumer_url An URL representing the consumer
78 * @param OMB_Datastore $datastore An instance of a class implementing
83 public function __construct ($service_url, $consumer_url, $datastore) {
84 $this->url = $service_url;
85 $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
86 $this->datastore = $datastore;
87 $this->oauth_consumer = new OAuthConsumer($consumer_url, '');
89 $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
91 /* Detect our services. This performs a validation as well, since
92 getService und getXRD throw exceptions on failure. */
93 $this->services = array();
95 foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
96 OMB_VERSION => OMB_Helper::$OMB_SERVICES)
97 as $service_root => $targetservices) {
98 $uris = $xrds->getService($service_root)->getURIs();
99 $xrd = $xrds->getXRD($uris[0]);
100 foreach ($targetservices as $targetservice) {
101 $yadis_service = $xrd->getService($targetservice);
102 if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
103 $localid = $yadis_service->getElements('xrd:LocalID');
104 $this->listener_uri = $yadis_service->parser->content($localid[0]);
106 $uris = $yadis_service->getURIs();
107 $this->services[$targetservice] = $uris[0];
113 * Get the handler URI for a service
115 * Returns the URI the remote web service has specified for the given
118 * @param string $service The URI identifying the service
122 * @return string The service handler URI
124 public function getServiceURI($service) {
125 return $this->services[$service];
129 * Get the remote user’s URI
131 * Returns the URI of the remote user, i. e. the listener.
135 * @return string The remote user’s URI
137 public function getRemoteUserURI() {
138 return $this->listener_uri;
142 * Get the listenee’s URI
144 * Returns the URI of the user being subscribed to, i. e. the local user.
148 * @return string The local user’s URI
150 public function getListeneeURI() {
151 return $this->listenee_uri;
155 * Request a request token
157 * Performs a token request on the service. Returns an OAuthToken on success.
158 * Throws an exception if the request fails.
162 * @return OAuthToken An unauthorized request token
164 public function requestToken() {
165 /* Set the token to null just in case the user called setToken. */
168 $result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
169 array('omb_listener' => $this->listener_uri));
170 if ($result->status != 200) {
171 throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
174 parse_str($result->body, $return);
175 if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
176 throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
179 $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
185 * Request authorization
187 * Returns an URL which equals to an authorization request. The end user
188 * should be redirected to this location to perform authorization.
189 * The $finish_url should be a local resource which invokes
190 * OMB_Consumer::finishAuthorization on request.
192 * @param OMB_Profile $profile An OMB_Profile object representing the
193 * soon-to-be subscribed (i. e. local) user
194 * @param string $finish_url Target location after successful
199 * @return string An URL representing an authorization request
201 public function requestAuthorization($profile, $finish_url) {
202 if ($this->performLegacyAuthRequest) {
203 $params = $profile->asParameters('omb_listenee', false);
204 $params['omb_listener'] = $this->listener_uri;
205 $params['oauth_callback'] = $finish_url;
207 $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
211 'oauth_callback' => $finish_url,
212 'oauth_token' => $this->token->key,
213 'omb_version' => OMB_VERSION,
214 'omb_listener' => $this->listener_uri);
216 $params = array_merge($profile->asParameters('omb_listenee', false). $params);
218 /* Build result URL. */
219 $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE];
220 $url .= (strrpos($url, '?') === false ? '?' : '&');
221 foreach ($params as $k => $v) {
222 $url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&';
226 $this->listenee_uri = $profile->getIdentifierURI();
232 * Finish authorization
234 * Finish the subscription process by converting the received and authorized
235 * request token into an access token. After that, the subscriber’s profile
236 * and the subscription are stored in the database.
237 * Expects an OAuthRequest in query parameters.
238 * Throws exceptions on failure.
242 public function finishAuthorization() {
243 OMB_Helper::removeMagicQuotesFromRequest();
244 $req = OAuthRequest::from_request();
245 if ($req->get_parameter('oauth_token') !=
247 /* That’s not the token I wanted to get authorized. */
248 throw new OAuthException('The authorized token does not equal the ' .
252 if ($req->get_parameter('omb_version') != OMB_VERSION) {
253 throw new OMB_RemoteServiceException('The remote service uses an ' .
254 'unsupported OMB version');
257 /* Construct the profile to validate it. */
259 /* Fix OMB bug. Listener URI is not passed. */
260 if ($_SERVER['REQUEST_METHOD'] == 'POST') {
265 $params['omb_listener'] = $this->listener_uri;
267 require_once 'profile.php';
268 $listener = OMB_Profile::fromParameters($params, 'omb_listener');
270 /* Ask the remote service to convert the authorized request token into an
273 $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
274 if ($result->status != 200) {
275 throw new OAuthException('Could not get access token');
278 parse_str($result->body, $return);
279 if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) {
280 throw new OAuthException('Could not get access token');
282 $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
284 /* Subscription is finished and valid. Now store the new subscriber and the
285 subscription in the database. */
287 $this->datastore->saveProfile($listener);
288 $this->datastore->saveSubscription($this->listener_uri,
294 * Return the URI identifying the listener
296 * Returns the URI for the OMB user who tries to subscribe or already has
297 * subscribed our user. This method is a workaround for a serious OMB flaw:
298 * The Listener URI is not passed in the finishauthorization call.
302 * @return string the listener’s URI
304 public function getListenerURI() {
305 return $this->listener_uri;
309 * Inform the service about a profile update
311 * Sends an updated profile to the service.
313 * @param OMB_Profile $profile The profile that has changed
317 public function updateProfile($profile) {
318 $params = $profile->asParameters('omb_listenee', true);
319 $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
323 * Inform the service about a new notice
325 * Sends a notice to the service.
327 * @param OMB_Notice $notice The notice
331 public function postNotice($notice) {
332 $params = $notice->asParameters();
333 $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
334 $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']);
338 * Set the token member variable
340 * Initializes the token based on given token and secret token.
342 * @param string $token The token
343 * @param string $secret The secret token
347 public function setToken($token, $secret) {
348 $this->token = new OAuthToken($token, $secret);
352 * Prepare an OAuthRequest object
354 * Creates an OAuthRequest object mapping the request specified by the
357 * @param string $action_uri The URI specifying the target service
358 * @param array $params Additional parameters for the service call
359 * @param string $method The HTTP method used to call the service
360 * ('POST' or 'GET', usually)
364 * @return OAuthRequest the prepared request
366 protected function prepareAction($action_uri, $params, $method) {
367 $url = $this->services[$action_uri];
369 $url_params = array();
370 parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
372 /* Add OMB version. */
373 $url_params['omb_version'] = OMB_VERSION;
375 /* Add user-defined parameters. */
376 $url_params = array_merge($url_params, $params);
378 $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
379 $this->token, $method, $url, $url_params);
381 /* Sign the request. */
382 $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
383 $this->oauth_consumer, $this->token);
389 * Perform a service call
391 * Creates an OAuthRequest object and execute the mapped call as POST request.
393 * @param string $action_uri The URI specifying the target service
394 * @param array $params Additional parameters for the service call
398 * @return Auth_Yadis_HTTPResponse The POST request response
400 protected function performAction($action_uri, $params) {
401 $req = $this->prepareAction($action_uri, $params, 'POST');
403 /* Return result page. */
404 return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
408 * Perform an OMB action
410 * Executes an OMB action – to date, it’s one of updateProfile or postNotice.
412 * @param string $action_uri The URI specifying the target service
413 * @param array $params Additional parameters for the service call
414 * @param string $listenee_uri The URI identifying the local user for whom
415 * the action is performed
419 protected function performOMBAction($action_uri, $params, $listenee_uri) {
420 $result = $this->performAction($action_uri, $params);
421 if ($result->status == 403) {
422 /* The remote user unsubscribed us. */
423 $this->datastore->deleteSubscription($this->listener_uri, $listenee_uri);
424 } else if ($result->status != 200 ||
425 strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
426 /* The server signaled an error or sent an incorrect response. */
427 throw OMB_RemoteServiceException::fromYadis($action_uri, $result);