]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/libomb/service_consumer.php
Merge branch '0.8.x' into 0.9.x
[quix0rs-gnu-social.git] / extlib / libomb / service_consumer.php
1 <?php
2
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';
11
12 /**
13  * OMB service representation
14  *
15  * This class represents a complete remote OMB service. It provides discovery
16  * and execution of the service’s methods.
17  *
18  * PHP version 5
19  *
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.
24  *
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.
29  *
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/>.
32  *
33  * @package   OMB
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
37  **/
38
39 class OMB_Service_Consumer {
40   protected $url; /* The service URL */
41   protected $services; /* An array of strings mapping service URI to
42                           service URL */
43
44   protected $token; /* An OAuthToken */
45
46   protected $listener_uri; /* The URI identifying the listener, i. e. the
47                               remote user. */
48
49   protected $listenee_uri; /* The URI identifying the listenee, i. e. the
50                               local user during an auth request. */
51
52   /**
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.
56    *
57    * Since Laconica up to version 0.7.2 performs a full OAuth request check, a
58    * correct request would fail.
59    **/
60   public $performLegacyAuthRequest = true;
61
62   /* Helper stuff we are going to need. */
63   protected $fetcher;
64   protected $oauth_consumer;
65   protected $datastore;
66
67   /**
68    * Constructor for OMB_Service_Consumer
69    *
70    * Initializes an OMB_Service_Consumer object representing the OMB service
71    * specified by $service_url. Performs a complete service discovery using
72    * Yadis.
73    * Throws OMB_UnsupportedServiceException if XRDS file does not specify a
74    * complete OMB service.
75    *
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
79    *                                    OMB_Datastore
80    *
81    * @access public
82    **/
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, '');
88
89     $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher);
90
91     /* Detect our services. This performs a validation as well, since
92        getService und getXRD throw exceptions on failure. */
93     $this->services = array();
94
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]);
105         }
106         $uris = $yadis_service->getURIs();
107         $this->services[$targetservice] = $uris[0];
108       }
109     }
110   }
111
112   /**
113    * Get the handler URI for a service
114    *
115    * Returns the URI the remote web service has specified for the given
116    * service.
117    *
118    * @param string $service The URI identifying the service
119    *
120    * @access public
121    *
122    * @return string The service handler URI
123    **/
124   public function getServiceURI($service) {
125     return $this->services[$service];
126   }
127
128   /**
129    * Get the remote user’s URI
130    *
131    * Returns the URI of the remote user, i. e. the listener.
132    *
133    * @access public
134    *
135    * @return string The remote user’s URI
136    **/
137   public function getRemoteUserURI() {
138     return $this->listener_uri;
139   }
140
141   /**
142    * Get the listenee’s URI
143    *
144    * Returns the URI of the user being subscribed to, i. e. the local user.
145    *
146    * @access public
147    *
148    * @return string The local user’s URI
149    **/
150   public function getListeneeURI() {
151     return $this->listenee_uri;
152   }
153
154   /**
155    * Request a request token
156    *
157    * Performs a token request on the service. Returns an OAuthToken on success.
158    * Throws an exception if the request fails.
159    *
160    * @access public
161    *
162    * @return OAuthToken An unauthorized request token
163    **/
164   public function requestToken() {
165     /* Set the token to null just in case the user called setToken. */
166     $this->token = null;
167
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,
172                                                   $result);
173     }
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,
177                                                   $result);
178     }
179     $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
180     return $this->token;
181   }
182
183   /**
184    *
185    * Request authorization
186    *
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.
191    *
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
195    *                                authorization
196    *
197    * @access public
198    *
199    * @return string An URL representing an authorization request
200    **/
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;
206
207       $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url();
208     } else {
209
210       $params = array(
211                 'oauth_callback' => $finish_url,
212                 'oauth_token'    => $this->token->key,
213                 'omb_version'    => OMB_VERSION,
214                 'omb_listener'   => $this->listener_uri);
215
216       $params = array_merge($profile->asParameters('omb_listenee', false). $params);
217
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) . '&';
223       }
224     }
225
226     $this->listenee_uri = $profile->getIdentifierURI();
227
228     return $url;
229   }
230
231   /**
232    * Finish authorization
233    *
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.
239    *
240    * @access public
241    **/
242   public function finishAuthorization() {
243     OMB_Helper::removeMagicQuotesFromRequest();
244     $req = OAuthRequest::from_request();
245     if ($req->get_parameter('oauth_token') !=
246           $this->token->key) {
247       /* That’s not the token I wanted to get authorized. */
248       throw new OAuthException('The authorized token does not equal the ' .
249                                'submitted token.');
250     }
251
252     if ($req->get_parameter('omb_version') != OMB_VERSION) {
253       throw new OMB_RemoteServiceException('The remote service uses an ' .
254                                            'unsupported OMB version');
255     }
256
257     /* Construct the profile to validate it. */
258
259     /* Fix OMB bug. Listener URI is not passed. */
260     if ($_SERVER['REQUEST_METHOD'] == 'POST') {
261       $params = $_POST;
262     } else {
263       $params = $_GET;
264     }
265     $params['omb_listener'] = $this->listener_uri;
266
267     require_once 'profile.php';
268     $listener = OMB_Profile::fromParameters($params, 'omb_listener');
269
270     /* Ask the remote service to convert the authorized request token into an
271        access token. */
272
273     $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array());
274     if ($result->status != 200) {
275       throw new OAuthException('Could not get access token');
276     }
277
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');
281     }
282     $this->setToken($return['oauth_token'], $return['oauth_token_secret']);
283
284     /* Subscription is finished and valid. Now store the new subscriber and the
285        subscription in the database. */
286
287     $this->datastore->saveProfile($listener);
288     $this->datastore->saveSubscription($this->listener_uri,
289                                        $this->listenee_uri,
290                                        $this->token);
291   }
292
293   /**
294    * Return the URI identifying the listener
295    *
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.
299    *
300    * @access public
301    *
302    * @return string the listener’s URI
303    **/
304   public function getListenerURI() {
305     return $this->listener_uri;
306   }
307
308   /**
309    * Inform the service about a profile update
310    *
311    * Sends an updated profile to the service.
312    *
313    * @param OMB_Profile $profile The profile that has changed
314    *
315    * @access public
316    **/
317   public function updateProfile($profile) {
318     $params = $profile->asParameters('omb_listenee', true);
319     $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI());
320   }
321
322   /**
323    * Inform the service about a new notice
324    *
325    * Sends a notice to the service.
326    *
327    * @param OMB_Notice $notice The notice
328    *
329    * @access public
330    **/
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']);
335   }
336
337   /**
338    * Set the token member variable
339    *
340    * Initializes the token based on given token and secret token.
341    *
342    * @param string $token  The token
343    * @param string $secret The secret token
344    *
345    * @access public
346    **/
347   public function setToken($token, $secret) {
348     $this->token = new OAuthToken($token, $secret);
349   }
350
351   /**
352    * Prepare an OAuthRequest object
353    *
354    * Creates an OAuthRequest object mapping the request specified by the
355    * parameters.
356    *
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)
361    *
362    * @access protected
363    *
364    * @return OAuthRequest the prepared request
365    **/
366   protected function prepareAction($action_uri, $params, $method) {
367     $url = $this->services[$action_uri];
368
369     $url_params = array();
370     parse_str(parse_url($url, PHP_URL_QUERY), $url_params);
371
372     /* Add OMB version. */
373     $url_params['omb_version'] = OMB_VERSION;
374
375     /* Add user-defined parameters. */
376     $url_params = array_merge($url_params, $params);
377
378     $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer,
379                                       $this->token, $method, $url, $url_params);
380
381     /* Sign the request. */
382     $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),
383                                            $this->oauth_consumer, $this->token);
384
385     return $req;
386   }
387
388   /**
389    * Perform a service call
390    *
391    * Creates an OAuthRequest object and execute the mapped call as POST request.
392    *
393    * @param string $action_uri The URI specifying the target service
394    * @param array  $params     Additional parameters for the service call
395    *
396    * @access protected
397    *
398    * @return Auth_Yadis_HTTPResponse The POST request response
399    **/
400   protected function performAction($action_uri, $params) {
401     $req = $this->prepareAction($action_uri, $params, 'POST');
402
403     /* Return result page. */
404     return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array());
405   }
406
407   /**
408    * Perform an OMB action
409    *
410    * Executes an OMB action – to date, it’s one of updateProfile or postNotice.
411    *
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
416    *
417    * @access protected
418    **/
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);
428     }
429   }
430 }