]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/libomb/service_provider.php
Merge branch '0.8.x' 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    * The OMB_Profile passed to the constructor of OMB_Service_Provider should
115    * not represent the user specified in the authorization request, but the one
116    * currently logged in to the service. This condition being satisfied,
117    * handleUserAuth will check whether the listener specified in the request is
118    * identical to the logged in user.
119    *
120    * @access public
121    *
122    * @return OMB_Profile The profile of the soon-to-be subscribed, i. e. remote
123    *                     user
124    **/
125   public function handleUserAuth() {
126     OMB_Helper::removeMagicQuotesFromRequest();
127
128     /* Verify the request token. */
129
130     $this->token = $this->datastore->lookup_token(null, "request", $_GET['oauth_token']);
131     if (is_null($this->token)) {
132       throw new OAuthException('The given request token has not been issued ' .
133                                'by this service.');
134     }
135
136     /* Verify the OMB part. */
137
138     if ($_GET['omb_version'] !== OMB_VERSION) {
139       throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
140                                    'Wrong OMB version ' . $_GET['omb_version']);
141     }
142
143     if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
144       throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
145                                  'Wrong OMB listener ' . $_GET['omb_listener']);
146     }
147
148     foreach (array('omb_listenee', 'omb_listenee_profile',
149                    'omb_listenee_nickname', 'omb_listenee_license') as $param) {
150       if (!isset($_GET[$param]) || is_null($_GET[$param])) {
151         throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
152                                        "Required parameter '$param' not found");
153       }
154     }
155
156     /* Store given callback for later use. */
157     if (isset($_GET['oauth_callback']) && $_GET['oauth_callback'] !== '') {
158       $this->callback = $_GET['oauth_callback'];
159       if (!OMB_Helper::validateURL($this->callback)) {
160         throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
161                                               'Invalid callback URL specified');
162       }
163     }
164     $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
165
166     return $this->remote_user;
167   }
168
169   /**
170    * Continue the OAuth dance after user authorization
171    *
172    * Performs the appropriate actions after user answered the authorization
173    * request.
174    *
175    * @param bool $accepted Whether the user granted authorization
176    *
177    * @access public
178    *
179    * @return array A two-component array with the values:
180    *                - callback The callback URL or null if none given
181    *                - token    The authorized request token or null if not
182    *                           authorized.
183    **/
184   public function continueUserAuth($accepted) {
185     $callback = $this->callback;
186     if (!$accepted) {
187       $this->datastore->revoke_token($this->token->key);
188       $this->token = null;
189       /* TODO: The handling is probably wrong in terms of OAuth 1.0 but the way
190                laconica works. Moreover I don’t know the right way either. */
191
192     } else {
193       $this->datastore->authorize_token($this->token->key);
194       $this->datastore->saveProfile($this->remote_user);
195       $this->datastore->saveSubscription($this->user->getIdentifierURI(),
196                           $this->remote_user->getIdentifierURI(), $this->token);
197
198       if (!is_null($this->callback)) {
199         /* Callback wants to get some informations as well. */
200         $params = $this->user->asParameters('omb_listener', false);
201
202         $params['oauth_token'] = $this->token->key;
203         $params['omb_version'] = OMB_VERSION;
204
205         $callback .= (parse_url($this->callback, PHP_URL_QUERY) ? '&' : '?');
206         foreach ($params as $k => $v) {
207           $callback .= OAuthUtil::urlencode_rfc3986($k) . '=' .
208                        OAuthUtil::urlencode_rfc3986($v) . '&';
209         }
210       }
211     }
212     return array($callback, $this->token);
213   }
214
215   /**
216    * Echo an access token
217    *
218    * Outputs an access token for the query found in $_POST. OMB 0.1 specifies
219    * that the access token request has to be a POST even if OAuth allows GET as
220    * well.
221    *
222    * @access public
223    **/
224   public function writeAccessToken() {
225     OMB_Helper::removeMagicQuotesFromRequest();
226     echo $this->getOAuthServer()->fetch_access_token(
227                                             OAuthRequest::from_request('POST'));
228   }
229
230   /**
231    * Handle an updateprofile request
232    *
233    * Handles an updateprofile request posted to this service. Updates the
234    * profile through the OMB_Datastore.
235    *
236    * @access public
237    *
238    * @return OMB_Profile The updated profile
239    **/
240   public function handleUpdateProfile() {
241     list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_UPDATEPROFILE);
242     $profile->updateFromParameters($req->get_parameters(), 'omb_listenee');
243     $this->datastore->saveProfile($profile);
244     $this->finishOMBRequest();
245     return $profile;
246   }
247
248   /**
249    * Handle a postnotice request
250    *
251    * Handles a postnotice request posted to this service. Saves the notice
252    * through the OMB_Datastore.
253    *
254    * @access public
255    *
256    * @return OMB_Notice The received notice
257    **/
258   public function handlePostNotice() {
259     list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
260     require_once 'notice.php';
261     $notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
262     $this->datastore->saveNotice($notice);
263     $this->finishOMBRequest();
264     return $notice;
265   }
266
267   /**
268    * Handle an OMB request
269    *
270    * Performs common OMB request handling.
271    *
272    * @param string $uri The URI defining the OMB endpoint being served
273    *
274    * @access protected
275    *
276    * @return array(OAuthRequest, OMB_Profile)
277    **/
278   protected function handleOMBRequest($uri) {
279
280     OMB_Helper::removeMagicQuotesFromRequest();
281     $req = OAuthRequest::from_request('POST');
282     $listenee =  $req->get_parameter('omb_listenee');
283
284     try {
285         list($consumer, $token) = $this->getOAuthServer()->verify_request($req);
286     } catch (OAuthException $e) {
287       header('HTTP/1.1 403 Forbidden');
288       throw OMB_RemoteServiceException::forRequest($uri,
289                                    'Revoked accesstoken for ' . $listenee);
290     }
291
292     $version = $req->get_parameter('omb_version');
293     if ($version !== OMB_VERSION) {
294       header('HTTP/1.1 400 Bad Request');
295       throw OMB_RemoteServiceException::forRequest($uri,
296                                    'Wrong OMB version ' . $version);
297     }
298
299     $profile = $this->datastore->getProfile($listenee);
300     if (is_null($profile)) {
301       header('HTTP/1.1 400 Bad Request');
302       throw OMB_RemoteServiceException::forRequest($uri,
303                                    'Unknown remote profile ' . $listenee);
304     }
305
306     $subscribers = $this->datastore->getSubscriptions($listenee);
307     if (count($subscribers) === 0) {
308       header('HTTP/1.1 403 Forbidden');
309       throw OMB_RemoteServiceException::forRequest($uri,
310                                    'No subscriber for ' . $listenee);
311     }
312
313     return array($req, $profile);
314   }
315
316   /**
317    * Finishes an OMB request handling
318    *
319    * Performs common OMB request handling finishing.
320    *
321    * @access protected
322    **/
323   protected function finishOMBRequest() {
324     header('HTTP/1.1 200 OK');
325     header('Content-type: text/plain');
326     /* There should be no clutter but the version. */
327     echo "omb_version=" . OMB_VERSION;
328   }
329
330   /**
331    * Return an OAuthServer
332    *
333    * Checks whether the OAuthServer is null. If so, initializes it with a
334    * default value. Returns the OAuth server.
335    *
336    * @access protected
337    **/
338   protected function getOAuthServer() {
339     if (is_null($this->oauth_server)) {
340       $this->oauth_server = new OAuthServer($this->datastore);
341       $this->oauth_server->add_signature_method(
342                                           new OAuthSignatureMethod_HMAC_SHA1());
343     }
344     return $this->oauth_server;
345   }
346
347   /**
348    * Publish a notice
349    *
350    * Posts an OMB notice. This includes storing the notice and posting it to
351    * subscribed users.
352    *
353    * @param OMB_Notice $notice The new notice
354    *
355    * @access public
356    *
357    * @return array An array mapping subscriber URIs to the exception posting to
358    *               them has raised; Empty array if no exception occured
359    **/
360   public function postNotice($notice) {
361     $uri = $this->user->getIdentifierURI();
362
363     /* $notice is passed by reference and may change. */
364     $this->datastore->saveNotice($notice);
365     $subscribers = $this->datastore->getSubscriptions($uri);
366
367     /* No one to post to. */
368     if (is_null($subscribers)) {
369         return array();
370     }
371
372     require_once 'service_consumer.php';
373
374     $err = array();
375     foreach($subscribers as $subscriber) {
376       try {
377         $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
378         $service->setToken($subscriber['token'], $subscriber['secret']);
379         $service->postNotice($notice);
380       } catch (Exception $e) {
381         $err[$subscriber['uri']] = $e;
382         continue;
383       }
384     }
385     return $err;
386   }
387
388   /**
389    * Publish a profile update
390    *
391    * Posts the current profile as an OMB profile update. This includes updating
392    * the stored profile and posting it to subscribed users.
393    *
394    * @access public
395    *
396    * @return array An array mapping subscriber URIs to the exception posting to
397    *               them has raised; Empty array if no exception occured
398    **/
399   public function updateProfile() {
400     $uri = $this->user->getIdentifierURI();
401
402     $this->datastore->saveProfile($this->user);
403     $subscribers = $this->datastore->getSubscriptions($uri);
404
405     /* No one to post to. */
406     if (is_null($subscribers)) {
407         return array();
408     }
409
410     require_once 'service_consumer.php';
411
412     $err = array();
413     foreach($subscribers as $subscriber) {
414       try {
415         $service = new OMB_Service_Consumer($subscriber['uri'], $uri, $this->datastore);
416         $service->setToken($subscriber['token'], $subscriber['secret']);
417         $service->updateProfile($this->user);
418       } catch (Exception $e) {
419         $err[$subscriber['uri']] = $e;
420         continue;
421       }
422     }
423     return $err;
424   }
425 }