3 * This file is part of libomb
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.
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.
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/>.
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
27 require_once 'constants.php';
28 require_once 'helper.php';
29 require_once 'notice.php';
30 require_once 'remoteserviceexception.php';
33 * OMB service realization
35 * This class realizes a complete, simple OMB service.
37 class OMB_Service_Provider
39 protected $user; /* An OMB_Profile representing the user */
40 protected $datastore; /* AN OMB_Datastore */
42 protected $remote_user; /* An OMB_Profile representing the remote user
43 during the authorization process */
45 protected $oauth_server; /* An OAuthServer; should only be accessed via
49 * Initialize an OMB_Service_Provider object
51 * Constructs an OMB_Service_Provider instance that provides OMB services
52 * referring to a particular user.
54 * @param OMB_Profile $user An OMB_Profile; mandatory for XRDS
55 * output, user auth handling and OMB
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
65 public function __construct ($user = null, $datastore = null,
69 $this->datastore = $datastore;
70 $this->oauth_server = $oauth_server;
74 * Return the remote user during user authorization
76 * Returns an OMB_Profile representing the remote user during the user
77 * authorization request.
79 * @return OMB_Profile The remote user
81 public function getRemoteUser()
83 return $this->remote_user;
87 * Write a XRDS document
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.
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
99 * @return mixed Depends on the used OMB_XRDS_Writer; OMB_Plain_XRDS_Writer
102 public function writeXRDS($xrds_mapper, $xrds_writer = null)
104 if ($xrds_writer == null) {
105 require_once 'plain_xrds_writer.php';
106 $xrds_writer = new OMB_Plain_XRDS_Writer();
108 return $xrds_writer->writeXRDS($this->user, $xrds_mapper);
112 * Echo a request token
114 * Outputs an unauthorized request token for the query found in $_GET or
119 public function writeRequestToken()
121 OMB_Helper::removeMagicQuotesFromRequest();
122 echo $this->getOAuthServer()->fetch_request_token(
123 OAuthRequest::from_request());
127 * Handle an user authorization request.
129 * Parses an authorization request. This includes OAuth and OMB
131 * Throws exceptions on failures. Returns an OMB_Profile object representing
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.
142 * @return OMB_Profile The profile of the soon-to-be subscribed, i. e.
145 public function handleUserAuth()
147 OMB_Helper::removeMagicQuotesFromRequest();
149 /* Verify the request token. */
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.');
158 /* Verify the OMB part. */
160 if ($_GET['omb_version'] !== OMB_VERSION) {
161 throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
162 'Wrong OMB version ' .
163 $_GET['omb_version']);
166 if ($_GET['omb_listener'] !== $this->user->getIdentifierURI()) {
167 throw OMB_RemoteServiceException::forRequest(OAUTH_ENDPOINT_AUTHORIZE,
168 'Wrong OMB listener ' .
169 $_GET['omb_listener']);
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");
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');
190 $this->remote_user = OMB_Profile::fromParameters($_GET, 'omb_listenee');
192 return $this->remote_user;
196 * Continue the OAuth dance after user authorization
198 * Performs the appropriate actions after user answered the authorization
201 * @param bool $accepted Whether the user granted authorization
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
210 public function continueUserAuth($accepted)
212 $callback = $this->callback;
214 $this->datastore->revoke_token($this->token->key);
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(),
224 if (!is_null($this->callback)) {
225 /* Callback wants to get some informations as well. */
226 $params = $this->user->asParameters('omb_listener', false);
228 $params['oauth_token'] = $this->token->key;
229 $params['omb_version'] = OMB_VERSION;
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) . '&';
238 return array($callback, $this->token);
242 * Echo an access token
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
250 public function writeAccessToken()
252 OMB_Helper::removeMagicQuotesFromRequest();
253 echo $this->getOAuthServer()->fetch_access_token(
254 OAuthRequest::from_request('POST'));
258 * Handle an updateprofile request
260 * Handles an updateprofile request posted to this service. Updates the
261 * profile through the OMB_Datastore.
265 * @return OMB_Profile The updated profile
267 public function handleUpdateProfile()
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();
277 * Handle a postnotice request
279 * Handles a postnotice request posted to this service. Saves the notice
280 * through the OMB_Datastore.
284 * @return OMB_Notice The received notice
286 public function handlePostNotice()
288 list($req, $profile) = $this->handleOMBRequest(OMB_ENDPOINT_POSTNOTICE);
290 $notice = OMB_Notice::fromParameters($profile, $req->get_parameters());
291 $this->datastore->saveNotice($notice);
292 $this->finishOMBRequest();
298 * Handle an OMB request
300 * Performs common OMB request handling.
302 * @param string $uri The URI defining the OMB endpoint being served
306 * @return array(OAuthRequest, OMB_Profile)
308 protected function handleOMBRequest($uri)
310 OMB_Helper::removeMagicQuotesFromRequest();
311 $req = OAuthRequest::from_request('POST');
312 $listenee = $req->get_parameter('omb_listenee');
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);
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);
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);
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);
343 return array($req, $profile);
347 * Finishes an OMB request handling
349 * Performs common OMB request handling finishing.
353 protected function finishOMBRequest()
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;
362 * Return an OAuthServer
364 * Checks whether the OAuthServer is null. If so, initializes it with a
365 * default value. Returns the OAuth server.
369 protected function getOAuthServer()
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());
376 return $this->oauth_server;
382 * Posts an OMB notice. This includes storing the notice and posting it to
385 * @param OMB_Notice $notice The new notice
389 * @return array An array mapping subscriber URIs to the exception posting
390 * to them has raised; Empty array if no exception occured
392 public function postNotice($notice)
394 $uri = $this->user->getIdentifierURI();
396 /* $notice is passed by reference and may change. */
397 $this->datastore->saveNotice($notice);
398 $subscribers = $this->datastore->getSubscriptions($uri);
400 /* No one to post to. */
401 if (is_null($subscribers)) {
405 require_once 'service_consumer.php';
408 foreach ($subscribers as $subscriber) {
410 $service = new OMB_Service_Consumer($subscriber['uri'], $uri,
412 $service->setToken($subscriber['token'], $subscriber['secret']);
413 $service->postNotice($notice);
414 } catch (Exception $e) {
415 $err[$subscriber['uri']] = $e;
423 * Publish a profile update
425 * Posts the current profile as an OMB profile update. This includes
426 * updating the stored profile and posting it to subscribed users.
430 * @return array An array mapping subscriber URIs to the exception posting
431 * to them has raised; Empty array if no exception occured
433 public function updateProfile()
435 $uri = $this->user->getIdentifierURI();
437 $this->datastore->saveProfile($this->user);
438 $subscribers = $this->datastore->getSubscriptions($uri);
440 /* No one to post to. */
441 if (is_null($subscribers)) {
445 require_once 'service_consumer.php';
448 foreach ($subscribers as $subscriber) {
450 $service = new OMB_Service_Consumer($subscriber['uri'], $uri,
452 $service->setToken($subscriber['token'], $subscriber['secret']);
453 $service->updateProfile($this->user);
454 } catch (Exception $e) {
455 $err[$subscriber['uri']] = $e;