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