]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/libomb/service_consumer.php
33ebccc654410c16cc23e4d3cf294e4e377d4bf3
[quix0rs-gnu-social.git] / extlib / libomb / service_consumer.php
1 <?php
2 /**
3  * This file is part of libomb
4  *
5  * PHP version 5
6  *
7  * LICENSE: This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  * @package OMB
21  * @author  Adrian Lang <mail@adrianlang.de>
22  * @license http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0
23  * @version 0.1a-20090828
24  * @link    http://adrianlang.de/libomb
25  */
26
27 require_once 'Validate.php';
28 require_once 'Auth/Yadis/Yadis.php';
29 require_once 'OAuth.php';
30 require_once 'constants.php';
31 require_once 'helper.php';
32 require_once 'omb_yadis_xrds.php';
33 require_once 'profile.php';
34 require_once 'remoteserviceexception.php';
35 require_once 'unsupportedserviceexception.php';
36
37 /**
38  * OMB service representation
39  *
40  * This class represents a complete remote OMB service. It provides discovery
41  * and execution of the service’s methods.
42  */
43 class OMB_Service_Consumer
44 {
45     protected $url; /* The service URL */
46     protected $services; /* An array of strings mapping service URI to
47                             service URL */
48
49     protected $token; /* An OAuthToken */
50
51     protected $listener_uri; /* The URI identifying the listener, i. e. the
52                                 remote user. */
53
54     protected $listenee_uri; /* The URI identifying the listenee, i. e. the
55                                 local user during an auth request. */
56
57     /**
58      * According to OAuth Core 1.0, an user authorization request is no
59      * full-blown OAuth request. nonce, timestamp, consumer_key and signature
60      * are not needed in this step. See http://laconi.ca/trac/ticket/827 for
61      * more informations.
62      *
63      * Since Laconica up to version 0.7.2 performs a full OAuth request check, a
64      * correct request would fail.
65      */
66     public $performLegacyAuthRequest = true;
67
68     /* Helper stuff we are going to need. */
69     protected $fetcher;
70     protected $oauth_consumer;
71     protected $datastore;
72
73     /**
74      * Constructor for OMB_Service_Consumer
75      *
76      * Initializes an OMB_Service_Consumer object representing the OMB service
77      * specified by $service_url. Performs a complete service discovery using
78      * Yadis.
79      * Throws OMB_UnsupportedServiceException if XRDS file does not specify a
80      * complete OMB service.
81      *
82      * @param string        $service_url  The URL of the service
83      * @param string        $consumer_url An URL representing the consumer
84      * @param OMB_Datastore $datastore    An instance of a class implementing
85      *                                    OMB_Datastore
86      *
87      * @access public
88      */
89     public function __construct ($service_url, $consumer_url, $datastore)
90     {
91         $this->url            = $service_url;
92         $this->fetcher        = Auth_Yadis_Yadis::getHTTPFetcher();
93         $this->datastore      = $datastore;
94         $this->oauth_consumer = new OAuthConsumer($consumer_url, '');
95
96         $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
97
98         /* Detect our services. This performs a validation as well, since
99             getService und getXRD throw exceptions on failure. */
100         $this->services = array();
101
102         foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES,
103                        OMB_VERSION     => OMB_Helper::$OMB_SERVICES)
104                  as    $service_root   => $targetservices) {
105             $uris = $xrds->getService($service_root)->getURIs();
106             $xrd  = $xrds->getXRD($uris[0]);
107             foreach ($targetservices as $targetservice) {
108                 $yadis_service = $xrd->getService($targetservice);
109                 if ($targetservice == OAUTH_ENDPOINT_REQUEST) {
110                         $localid            =
111                                    $yadis_service->getElements('xrd:LocalID');
112                         $this->listener_uri =
113                                    $yadis_service->parser->content($localid[0]);
114                 }
115                 $uris                           = $yadis_service->getURIs();
116                 $this->services[$targetservice] = $uris[0];
117             }
118         }
119     }
120
121     /**
122      * Get the handler URI for a service
123      *
124      * Returns the URI the remote web service has specified for the given
125      * service.
126      *
127      * @param string $service The URI identifying the service
128      *
129      * @access public
130      *
131      * @return string The service handler URI
132      */
133     public function getServiceURI($service)
134     {
135         return $this->services[$service];
136     }
137
138     /**
139      * Get the remote user’s URI
140      *
141      * Returns the URI of the remote user, i. e. the listener.
142      *
143      * @access public
144      *
145      * @return string The remote user’s URI
146      */
147     public function getRemoteUserURI()
148     {
149         return $this->listener_uri;
150     }
151
152     /**
153      * Get the listenee’s URI
154      *
155      * Returns the URI of the user being subscribed to, i. e. the local user.
156      *
157      * @access public
158      *
159      * @return string The local user’s URI
160      */
161     public function getListeneeURI()
162     {
163         return $this->listenee_uri;
164     }
165
166     /**
167      * Request a request token
168      *
169      * Performs a token request on the service. Returns an OAuthToken on success.
170      * Throws an exception if the request fails.
171      *
172      * @access public
173      *
174      * @return OAuthToken An unauthorized request token
175      */
176     public function requestToken()
177     {
178         /* Set the token to null just in case the user called setToken. */
179         $this->token = null;
180
181         $result = $this->performAction(OAUTH_ENDPOINT_REQUEST,
182                                        array('omb_listener' => $this->listener_uri));
183         if ($result->status != 200) {
184             throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
185                                                         $result);
186         }
187         parse_str($result->body, $return);
188         if (!isset($return['oauth_token']) ||
189             !isset($return['oauth_token_secret'])) {
190             throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST,
191                                                         $result);
192         }
193         $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
194         return $this->token;
195     }
196
197     /**
198      * Request authorization
199      *
200      * Returns an URL which equals to an authorization request. The end user
201      * should be redirected to this location to perform authorization.
202      * The $finish_url should be a local resource which invokes
203      * OMB_Consumer::finishAuthorization on request.
204      *
205      * @param OMB_Profile $profile    An OMB_Profile object representing the
206      *                                soon-to-be subscribed (i. e. local) user
207      * @param string      $finish_url Target location after successful
208      *                                authorization
209      *
210      * @access public
211      *
212      * @return string An URL representing an authorization request
213      */
214     public function requestAuthorization($profile, $finish_url)
215     {
216         if ($this->performLegacyAuthRequest) {
217             $params                   = $profile->asParameters('omb_listenee',
218                                                                false);
219             $params['omb_listener']   = $this->listener_uri;
220             $params['oauth_callback'] = $finish_url;
221
222             $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params,
223                                         'GET')->to_url();
224         } else {
225             $params = array('oauth_callback' => $finish_url,
226                             'oauth_token'    => $this->token->key,
227                             'omb_version'    => OMB_VERSION,
228                             'omb_listener'   => $this->listener_uri);
229
230             $params = array_merge($profile->asParameters('omb_listenee', false),
231                                   $params);
232
233             /* Build result URL. */
234             $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE] .
235                    (strrpos($url, '?') === false ? '?' : '&');
236             foreach ($params as $k => $v) {
237                 $url .= OAuthUtil::urlencode_rfc3986($k) . '=' .
238                         OAuthUtil::urlencode_rfc3986($v) . '&';
239             }
240         }
241
242         $this->listenee_uri = $profile->getIdentifierURI();
243
244         return $url;
245     }
246
247     /**
248      * Finish authorization
249      *
250      * Finish the subscription process by converting the received and authorized
251      * request token into an access token. After that, the subscriber’s profile
252      * and the subscription are stored in the database.
253      * Expects an OAuthRequest in query parameters.
254      * Throws exceptions on failure.
255      *
256      * @access public
257      */
258     public function finishAuthorization()
259     {
260         OMB_Helper::removeMagicQuotesFromRequest();
261         $req = OAuthRequest::from_request();
262         if ($req->get_parameter('oauth_token') != $this->token->key) {
263             /* That’s not the token I wanted to get authorized. */
264             throw new OAuthException('The authorized token does not equal ' .
265                                      'the submitted token.');
266         }
267
268         if ($req->get_parameter('omb_version') != OMB_VERSION) {
269             throw new OMB_RemoteServiceException('The remote service uses an ' .
270                                                  'unsupported OMB version');
271         }
272
273         /* Construct the profile to validate it. */
274
275         /* Fix OMB bug. Listener URI is not passed. */
276         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
277             $params = $_POST;
278         } else {
279             $params = $_GET;
280         }
281         $params['omb_listener'] = $this->listener_uri;
282
283         $listener = OMB_Profile::fromParameters($params, 'omb_listener');
284
285         /* Ask the remote service to convert the authorized request token into
286            an access token. */
287
288         $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
289         if ($result->status != 200) {
290             throw new OAuthException('Could not get access token');
291         }
292
293         parse_str($result->body, $return);
294         if (!isset($return['oauth_token']) ||
295             !isset($return['oauth_token_secret'])) {
296             throw new OAuthException('Could not get access token');
297         }
298         $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
299
300         /* Subscription is finished and valid. Now store the new subscriber and
301            the subscription in the database. */
302
303         $this->datastore->saveProfile($listener);
304         $this->datastore->saveSubscription($this->listener_uri,
305                                            $this->listenee_uri,
306                                            $this->token);
307     }
308
309     /**
310      * Return the URI identifying the listener
311      *
312      * Returns the URI for the OMB user who tries to subscribe or already has
313      * subscribed our user. This method is a workaround for a serious OMB flaw:
314      * The Listener URI is not passed in the finishauthorization call.
315      *
316      * @access public
317      *
318      * @return string the listener’s URI
319      */
320     public function getListenerURI()
321     {
322         return $this->listener_uri;
323     }
324
325     /**
326      * Inform the service about a profile update
327      *
328      * Sends an updated profile to the service.
329      *
330      * @param OMB_Profile $profile The profile that has changed
331      *
332      * @access public
333      */
334     public function updateProfile($profile)
335     {
336         $params = $profile->asParameters('omb_listenee', true);
337         $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params,
338                                 $profile->getIdentifierURI());
339     }
340
341     /**
342      * Inform the service about a new notice
343      *
344      * Sends a notice to the service.
345      *
346      * @param OMB_Notice $notice The notice
347      *
348      * @access public
349      */
350     public function postNotice($notice)
351     {
352         $params                 = $notice->asParameters();
353         $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI();
354         $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params,
355                                 $params['omb_listenee']);
356     }
357
358     /**
359      * Set the token member variable
360      *
361      * Initializes the token based on given token and secret token.
362      *
363      * @param string $token  The token
364      * @param string $secret The secret token
365      *
366      * @access public
367      */
368     public function setToken($token, $secret)
369     {
370         $this->token = new OAuthToken($token, $secret);
371     }
372
373     /**
374      * Prepare an OAuthRequest object
375      *
376      * Creates an OAuthRequest object mapping the request specified by the
377      * parameters.
378      *
379      * @param string $action_uri The URI specifying the target service
380      * @param array  $params     Additional parameters for the service call
381      * @param string $method     The HTTP method used to call the service
382      *                           ('POST' or 'GET', usually)
383      *
384      * @access protected
385      *
386      * @return OAuthRequest the prepared request
387      */
388     protected function prepareAction($action_uri, $params, $method)
389     {
390         $url = $this->services[$action_uri];
391
392         $url_params = array();
393         parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
394
395         /* Add OMB version. */
396         $url_params['omb_version'] = OMB_VERSION;
397
398         /* Add user-defined parameters. */
399         $url_params = array_merge($url_params, $params);
400
401         $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
402                                                      $this->token, $method,
403                                                      $url, $url_params);
404
405         /* Sign the request. */
406         $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
407                            $this->oauth_consumer, $this->token);
408
409         return $req;
410     }
411
412     /**
413      * Perform a service call
414      *
415      * Creates an OAuthRequest object and execute the mapped call as POST
416      * request.
417      *
418      * @param string $action_uri The URI specifying the target service
419      * @param array  $params     Additional parameters for the service call
420      *
421      * @access protected
422      *
423      * @return Auth_Yadis_HTTPResponse The POST request response
424      */
425     protected function performAction($action_uri, $params)
426     {
427         $req = $this->prepareAction($action_uri, $params, 'POST');
428
429         /* Return result page. */
430         return $this->fetcher->post($req->get_normalized_http_url(),
431                                     $req->to_postdata(), array());
432     }
433
434     /**
435      * Perform an OMB action
436      *
437      * Executes an OMB action – as of OMB 0.1, it’s one of updateProfile and
438      * postNotice.
439      *
440      * @param string $action_uri   The URI specifying the target service
441      * @param array  $params       Additional parameters for the service call
442      * @param string $listenee_uri The URI identifying the local user for whom
443      *                             the action is performed
444      *
445      * @access protected
446      */
447     protected function performOMBAction($action_uri, $params, $listenee_uri)
448     {
449         $result = $this->performAction($action_uri, $params);
450         if ($result->status == 403) {
451             /* The remote user unsubscribed us. */
452             $this->datastore->deleteSubscription($this->listener_uri,
453                                                  $listenee_uri);
454         } else if ($result->status != 200 ||
455                    strpos($result->body, 'omb_version=' . OMB_VERSION) === false) {
456             /* The server signaled an error or sent an incorrect response. */
457             throw OMB_RemoteServiceException::fromYadis($action_uri, $result);
458         }
459     }
460 }
461 ?>