]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/OMB/extlib/libomb/service_provider.php
9a1a488a6b4db57a2073ee0ff4690599e194c70c
[quix0rs-gnu-social.git] / plugins / OMB / extlib / libomb / service_provider.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 'constants.php';
28 require_once 'helper.php';
29 require_once 'notice.php';
30 require_once 'remoteserviceexception.php';
31
32 /**
33  * OMB service realization
34  *
35  * This class realizes a complete, simple OMB service.
36  */
37 class OMB_Service_Provider
38 {
39     protected $user; /* An OMB_Profile representing the user */
40     protected $datastore; /* AN OMB_Datastore */
41
42     protected $remote_user; /* An OMB_Profile representing the remote user
43                                during the authorization process */
44
45     protected $oauth_server; /* An OAuthServer; should only be accessed via
46                                 getOAuthServer. */
47
48     /**
49      * Initialize an OMB_Service_Provider object
50      *
51      * Constructs an OMB_Service_Provider instance that provides OMB services
52      * referring to a particular user.
53      *
54      * @param OMB_Profile   $user         An OMB_Profile; mandatory for XRDS
55      *                                    output, user auth handling and OMB
56      *                                    action performing
57      * @param OMB_Datastore $datastore    An OMB_Datastore; mandatory for
58      *                                    everything but XRDS output
59      * @param OAuthServer   $oauth_server An OAuthServer; used for token writing
60      *                                    and OMB action handling; will use
61      *                                    default value if not set
62      *
63      * @access public
64      */
65     public function __construct ($user = null, $datastore = null,
66                                  $oauth_server = null)
67     {
68         $this->user         = $user;
69         $this->datastore    = $datastore;
70         $this->oauth_server = $oauth_server;
71     }
72
73     /**
74      * Return the remote user during user authorization
75      *
76      * Returns an OMB_Profile representing the remote user during the user
77      * authorization request.
78      *
79      * @return OMB_Profile The remote user
80      */
81     public function getRemoteUser()
82     {
83         return $this->remote_user;
84     }
85
86     /**
87      * Write a XRDS document
88      *
89      * Writes a XRDS document specifying the OMB service. Optionally uses a
90      * given object of a class implementing OMB_XRDS_Writer for output. Else
91      * OMB_Plain_XRDS_Writer is used.
92      *
93      * @param OMB_XRDS_Mapper $xrds_mapper An object mapping actions to URLs
94      * @param OMB_XRDS_Writer $xrds_writer Optional; The OMB_XRDS_Writer used to
95      *                                     write the XRDS document
96      *
97      * @access public
98      *
99      * @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer
100      *               returns nothing.
101      */
102     public function writeXRDS($xrds_mapper, $xrds_writer = null)
103     {
104         if ($xrds_writer == null) {
105                 require_once 'plain_xrds_writer.php';
106                 $xrds_writer = new OMB_Plain_XRDS_Writer();
107         }
108         return $xrds_writer->writeXRDS($this->user, $xrds_mapper);
109     }
110
111     /**
112      * Echo a request token
113      *
114      * Outputs an unauthorized request token for the query found in $_GET or
115      * $_POST.
116      *
117      * @access public
118      */
119     public function writeRequestToken()
120     {
121         OMB_Helper::removeMagicQuotesFromRequest();
122         echo $this->getOAuthServer()->fetch_request_token(
123                                                   OAuthRequest::from_request());
124     }
125
126     /**
127      * Handle an user authorization request.
128      *
129      * Parses an authorization request. This includes OAuth and OMB
130      * verification.
131      * Throws exceptions on failures. Returns an OMB_Profile object representing
132      * the remote user.
133      *
134      * The OMB_Profile passed to the constructor of OMB_Service_Provider should
135      * not represent the user specified in the authorization request, but the
136      * one currently logged in to the service. This condition being satisfied,
137      * handleUserAuth will check whether the listener specified in the request
138      * is identical to the logged in user.
139      *
140      * @access public
141      *
142      * @return OMB_Profile The profile of the soon-to-be subscribed, i. e.
143      *                     remote user
144      */
145     public function handleUserAuth()
146     {
147         OMB_Helper::removeMagicQuotesFromRequest();
148
149         /* Verify the request token. */
150
151         $this->token = $this->datastore->lookup_token(null, "request",
152                                                       $_GET['oauth_token']);
153         if (is_null($this->token)) {
154             throw new OAuthException('The given request token has not been ' .
155                                      'issued by this service.');
156         }
157
158         /* Verify the OMB part. */
159
160         if ($_GET['omb_version'] !== OMB_VERSION) {
161             throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
162                                                          'Wrong OMB version ' .
163                                                          $_GET['omb_version']);
164         }
165
166         if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
167             throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
168                                                          'Wrong OMB listener ' .
169                                                          $_GET['omb_listener']);
170         }
171
172         foreach (array('omb_listenee', 'omb_listenee_profile',
173                        'omb_listenee_nickname', 'omb_listenee_license') as $param) {
174             if (!isset($_GET[$param]) || is_null($_GET[$param])) {
175                 throw OMB_RemoteServiceException::forRequest(
176                                        OAUTH_ENDPOINT_AUTHORIZE,
177                                        "Required parameter '$param' not found");
178             }
179         }
180
181         /* Store given callback for later use. */
182         if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') {
183             $this->callback = $_GET['oauth_callback'];
184             if (!OMB_Helper::validateURL($this->callback)) {
185                 throw OMB_RemoteServiceException::forRequest(
186                                         OAUTH_ENDPOINT_AUTHORIZE,
187                                         'Invalid callback URL specified');
188             }
189         }
190         $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
191
192         return $this->remote_user;
193     }
194
195     /**
196      * Continue the OAuth dance after user authorization
197      *
198      * Performs the appropriate actions after user answered the authorization
199      * request.
200      *
201      * @param bool $accepted Whether the user granted authorization
202      *
203      * @access public
204      *
205      * @return array A two-component array with the values:
206      *                 - callback The callback URL or null if none given
207      *                 - token    The authorized request token or null if not
208      *                            authorized.
209      */
210     public function continueUserAuth($accepted)
211     {
212         $callback = $this->callback;
213         if (!$accepted) {
214             $this->datastore->revoke_token($this->token->key);
215             $this->token = null;
216
217         } else {
218             $this->datastore->authorize_token($this->token->key);
219             $this->datastore->saveProfile($this->remote_user);
220             $this->datastore->saveSubscription($this->user->getIdentifierURI(),
221                                          $this->remote_user->getIdentifierURI(),
222                                          $this->token);
223
224             if (!is_null($this->callback)) {
225                 /* Callback wants to get some informations as well. */
226                 $params = $this->user->asParameters('omb_listener', false);
227
228                 $params['oauth_token'] = $this->token->key;
229                 $params['omb_version'] = OMB_VERSION;
230
231                 $callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
232                 foreach ($params as $k => $v) {
233                     $callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
234                                  OAuthUtil::urlencode_rfc3986($v) . '&';
235                 }
236             }
237         }
238         return array($callback, $this->token);
239     }
240
241     /**
242      * Echo an access token
243      *
244      * Outputs an access token for the query found in $_POST. OMB 0.1 specifies
245      * that the access token request has to be a POST even if OAuth allows GET
246      * as well.
247      *
248      * @access public
249      */
250     public function writeAccessToken()
251     {
252         OMB_Helper::removeMagicQuotesFromRequest();
253         echo $this->getOAuthServer()->fetch_access_token(
254                                             OAuthRequest::from_request('POST'));
255     }
256
257     /**
258      * Handle an updateprofile request
259      *
260      * Handles an updateprofile request posted to this service. Updates the
261      * profile through the OMB_Datastore.
262      *
263      * @access public
264      *
265      * @return OMB_Profile The updated profile
266      */
267     public function handleUpdateProfile()
268     {
269         list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE);
270         $profile->updateFromParameters($req->get_parameters(), 'omb_listenee');
271         $this->datastore->saveProfile($profile);
272         $this->finishOMBRequest();
273         return $profile;
274     }
275
276     /**
277      * Handle a postnotice request
278      *
279      * Handles a postnotice request posted to this service. Saves the notice
280      * through the OMB_Datastore.
281      *
282      * @access public
283      *
284      * @return OMB_Notice The received notice
285      */
286     public function handlePostNotice()
287     {
288         list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
289
290         $notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
291         $this->datastore->saveNotice($notice);
292         $this->finishOMBRequest();
293
294         return $notice;
295     }
296
297     /**
298      * Handle an OMB request
299      *
300      * Performs common OMB request handling.
301      *
302      * @param string $uri The URI defining the OMB endpoint being served
303      *
304      * @access protected
305      *
306      * @return array(OAuthRequest, OMB_Profile)
307      */
308     protected function handleOMBRequest($uri)
309     {
310         OMB_Helper::removeMagicQuotesFromRequest();
311         $req      = OAuthRequest::from_request('POST');
312         $listenee = $req->get_parameter('omb_listenee');
313
314         try {
315             list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
316         } catch (OAuthException $e) {
317             header('HTTP/1.1 403 Forbidden');
318             throw OMB_RemoteServiceException::forRequest($uri,
319                                        'Revoked accesstoken for ' . $listenee);
320         }
321
322         $version = $req->get_parameter('omb_version');
323         if ($version !== OMB_VERSION) {
324             header('HTTP/1.1 400 Bad Request');
325             throw OMB_RemoteServiceException::forRequest($uri,
326                                                'Wrong OMB version ' . $version);
327         }
328
329         $profile = $this->datastore->getProfile($listenee);
330         if (is_null($profile)) {
331             header('HTTP/1.1 400 Bad Request');
332             throw OMB_RemoteServiceException::forRequest($uri,
333                                          'Unknown remote profile ' . $listenee);
334         }
335
336         $subscribers = $this->datastore->getSubscriptions($listenee);
337         if (count($subscribers) === 0) {
338             header('HTTP/1.1 403 Forbidden');
339             throw OMB_RemoteServiceException::forRequest($uri,
340                                               'No subscriber for ' . $listenee);
341         }
342
343         return array($req, $profile);
344     }
345
346     /**
347      * Finishes an OMB request handling
348      *
349      * Performs common OMB request handling finishing.
350      *
351      * @access protected
352      */
353     protected function finishOMBRequest()
354     {
355         header('HTTP/1.1 200 OK');
356         header('Content-type: text/plain');
357         /* There should be no clutter but the version. */
358         echo "omb_version=" . OMB_VERSION;
359     }
360
361     /**
362      * Return an OAuthServer
363      *
364      * Checks whether the OAuthServer is null. If so, initializes it with a
365      * default value. Returns the OAuth server.
366      *
367      * @access protected
368      */
369     protected function getOAuthServer()
370     {
371         if (is_null($this->oauth_server)) {
372             $this->oauth_server = new OAuthServer($this->datastore);
373             $this->oauth_server->add_signature_method(
374                                           new OAuthSignatureMethod_HMAC_SHA1());
375         }
376         return $this->oauth_server;
377     }
378
379     /**
380      * Publish a notice
381      *
382      * Posts an OMB notice. This includes storing the notice and posting it to
383      * subscribed users.
384      *
385      * @param OMB_Notice $notice The new notice
386      *
387      * @access public
388      *
389      * @return array An array mapping subscriber URIs to the exception posting
390      *               to them has raised; Empty array if no exception occured
391      */
392     public function postNotice($notice)
393     {
394         $uri = $this->user->getIdentifierURI();
395
396         /* $notice is passed by reference and may change. */
397         $this->datastore->saveNotice($notice);
398         $subscribers = $this->datastore->getSubscriptions($uri);
399
400         /* No one to post to. */
401         if (is_null($subscribers)) {
402             return array();
403         }
404
405         require_once 'service_consumer.php';
406
407         $err = array();
408         foreach ($subscribers as $subscriber) {
409             try {
410                 $service = new OMB_Service_Consumer($subscriber['uri'], $uri,
411                                                     $this->datastore);
412                 $service->setToken($subscriber['token'], $subscriber['secret']);
413                 $service->postNotice($notice);
414             } catch (Exception $e) {
415                 $err[$subscriber['uri']] = $e;
416                 continue;
417             }
418         }
419         return $err;
420     }
421
422     /**
423      * Publish a profile update
424      *
425      * Posts the current profile as an OMB profile update. This includes
426      * updating the stored profile and posting it to subscribed users.
427      *
428      * @access public
429      *
430      * @return array An array mapping subscriber URIs to the exception posting
431      *               to them has raised; Empty array if no exception occured
432      */
433     public function updateProfile()
434     {
435         $uri = $this->user->getIdentifierURI();
436
437         $this->datastore->saveProfile($this->user);
438         $subscribers = $this->datastore->getSubscriptions($uri);
439
440         /* No one to post to. */
441         if (is_null($subscribers)) {
442                 return array();
443         }
444
445         require_once 'service_consumer.php';
446
447         $err = array();
448         foreach ($subscribers as $subscriber) {
449             try {
450                 $service = new OMB_Service_Consumer($subscriber['uri'], $uri,
451                                                     $this->datastore);
452                 $service->setToken($subscriber['token'], $subscriber['secret']);
453                 $service->updateProfile($this->user);
454             } catch (Exception $e) {
455                 $err[$subscriber['uri']] = $e;
456                 continue;
457             }
458         }
459         return $err;
460     }
461 }