431 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			431 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|   | <?php | |||
|  | 
 | |||
|  | require_once 'constants.php'; | |||
|  | require_once 'Validate.php'; | |||
|  | require_once 'Auth/Yadis/Yadis.php'; | |||
|  | require_once 'OAuth.php'; | |||
|  | require_once 'unsupportedserviceexception.php'; | |||
|  | require_once 'remoteserviceexception.php'; | |||
|  | require_once 'omb_yadis_xrds.php'; | |||
|  | require_once 'helper.php'; | |||
|  | 
 | |||
|  | /** | |||
|  |  * OMB service representation | |||
|  |  * | |||
|  |  * This class represents a complete remote OMB service. It provides discovery | |||
|  |  * and execution of the service’s methods. | |||
|  |  * | |||
|  |  * PHP version 5 | |||
|  |  * | |||
|  |  * LICENSE: This program is free software: you can redistribute it and/or modify | |||
|  |  * it under the terms of the GNU Affero General Public License as published by | |||
|  |  * the Free Software Foundation, either version 3 of the License, or | |||
|  |  * (at your option) any later version. | |||
|  |  * | |||
|  |  * This program is distributed in the hope that it will be useful, | |||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | |||
|  |  * GNU Affero General Public License for more details. | |||
|  |  * | |||
|  |  * You should have received a copy of the GNU Affero General Public License | |||
|  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | |||
|  |  * | |||
|  |  * @package   OMB | |||
|  |  * @author    Adrian Lang <mail@adrianlang.de> | |||
|  |  * @copyright 2009 Adrian Lang | |||
|  |  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL 3.0 | |||
|  |  **/ | |||
|  | 
 | |||
|  | class OMB_Service_Consumer { | |||
|  |   protected $url; /* The service URL */ | |||
|  |   protected $services; /* An array of strings mapping service URI to | |||
|  |                           service URL */ | |||
|  | 
 | |||
|  |   protected $token; /* An OAuthToken */ | |||
|  | 
 | |||
|  |   protected $listener_uri; /* The URI identifying the listener, i. e. the | |||
|  |                               remote user. */ | |||
|  | 
 | |||
|  |   protected $listenee_uri; /* The URI identifying the listenee, i. e. the | |||
|  |                               local user during an auth request. */ | |||
|  | 
 | |||
|  |   /** | |||
|  |    * According to OAuth Core 1.0, an user authorization request is no full-blown | |||
|  |    * OAuth request. nonce, timestamp, consumer_key and signature are not needed | |||
|  |    * in this step. See http://laconi.ca/trac/ticket/827 for more informations. | |||
|  |    * | |||
|  |    * Since Laconica up to version 0.7.2 performs a full OAuth request check, a | |||
|  |    * correct request would fail. | |||
|  |    **/ | |||
|  |   public $performLegacyAuthRequest = true; | |||
|  | 
 | |||
|  |   /* Helper stuff we are going to need. */ | |||
|  |   protected $fetcher; | |||
|  |   protected $oauth_consumer; | |||
|  |   protected $datastore; | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Constructor for OMB_Service_Consumer | |||
|  |    * | |||
|  |    * Initializes an OMB_Service_Consumer object representing the OMB service | |||
|  |    * specified by $service_url. Performs a complete service discovery using | |||
|  |    * Yadis. | |||
|  |    * Throws OMB_UnsupportedServiceException if XRDS file does not specify a | |||
|  |    * complete OMB service. | |||
|  |    * | |||
|  |    * @param string        $service_url  The URL of the service | |||
|  |    * @param string        $consumer_url An URL representing the consumer | |||
|  |    * @param OMB_Datastore $datastore    An instance of a class implementing | |||
|  |    *                                    OMB_Datastore | |||
|  |    * | |||
|  |    * @access public | |||
|  |    **/ | |||
|  |   public function __construct ($service_url, $consumer_url, $datastore) { | |||
|  |     $this->url = $service_url; | |||
|  |     $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); | |||
|  |     $this->datastore = $datastore; | |||
|  |     $this->oauth_consumer = new OAuthConsumer($consumer_url, ''); | |||
|  | 
 | |||
|  |     $xrds = OMB_Yadis_XRDS::fromYadisURL($service_url, $this->fetcher); | |||
|  | 
 | |||
|  |     /* Detect our services. This performs a validation as well, since | |||
|  |        getService und getXRD throw exceptions on failure. */ | |||
|  |     $this->services = array(); | |||
|  | 
 | |||
|  |     foreach (array(OAUTH_DISCOVERY => OMB_Helper::$OAUTH_SERVICES, | |||
|  |                    OMB_VERSION     => OMB_Helper::$OMB_SERVICES) | |||
|  |              as $service_root => $targetservices) { | |||
|  |       $uris = $xrds->getService($service_root)->getURIs(); | |||
|  |       $xrd = $xrds->getXRD($uris[0]); | |||
|  |       foreach ($targetservices as $targetservice) { | |||
|  |         $yadis_service = $xrd->getService($targetservice); | |||
|  |         if ($targetservice == OAUTH_ENDPOINT_REQUEST) { | |||
|  |             $localid = $yadis_service->getElements('xrd:LocalID'); | |||
|  |             $this->listener_uri = $yadis_service->parser->content($localid[0]); | |||
|  |         } | |||
|  |         $uris = $yadis_service->getURIs(); | |||
|  |         $this->services[$targetservice] = $uris[0]; | |||
|  |       } | |||
|  |     } | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Get the handler URI for a service | |||
|  |    * | |||
|  |    * Returns the URI the remote web service has specified for the given | |||
|  |    * service. | |||
|  |    * | |||
|  |    * @param string $service The URI identifying the service | |||
|  |    * | |||
|  |    * @access public | |||
|  |    * | |||
|  |    * @return string The service handler URI | |||
|  |    **/ | |||
|  |   public function getServiceURI($service) { | |||
|  |     return $this->services[$service]; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Get the remote user’s URI | |||
|  |    * | |||
|  |    * Returns the URI of the remote user, i. e. the listener. | |||
|  |    * | |||
|  |    * @access public | |||
|  |    * | |||
|  |    * @return string The remote user’s URI | |||
|  |    **/ | |||
|  |   public function getRemoteUserURI() { | |||
|  |     return $this->listener_uri; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Get the listenee’s URI | |||
|  |    * | |||
|  |    * Returns the URI of the user being subscribed to, i. e. the local user. | |||
|  |    * | |||
|  |    * @access public | |||
|  |    * | |||
|  |    * @return string The local user’s URI | |||
|  |    **/ | |||
|  |   public function getListeneeURI() { | |||
|  |     return $this->listenee_uri; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Request a request token | |||
|  |    * | |||
|  |    * Performs a token request on the service. Returns an OAuthToken on success. | |||
|  |    * Throws an exception if the request fails. | |||
|  |    * | |||
|  |    * @access public | |||
|  |    * | |||
|  |    * @return OAuthToken An unauthorized request token | |||
|  |    **/ | |||
|  |   public function requestToken() { | |||
|  |     /* Set the token to null just in case the user called setToken. */ | |||
|  |     $this->token = null; | |||
|  | 
 | |||
|  |     $result = $this->performAction(OAUTH_ENDPOINT_REQUEST, | |||
|  |                                   array('omb_listener' => $this->listener_uri)); | |||
|  |     if ($result->status != 200) { | |||
|  |       throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST, | |||
|  |                                                   $result); | |||
|  |     } | |||
|  |     parse_str($result->body, $return); | |||
|  |     if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) { | |||
|  |       throw OMB_RemoteServiceException::fromYadis(OAUTH_ENDPOINT_REQUEST, | |||
|  |                                                   $result); | |||
|  |     } | |||
|  |     $this->setToken($return['oauth_token'], $return['oauth_token_secret']); | |||
|  |     return $this->token; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * | |||
|  |    * Request authorization | |||
|  |    * | |||
|  |    * Returns an URL which equals to an authorization request. The end user | |||
|  |    * should be redirected to this location to perform authorization. | |||
|  |    * The $finish_url should be a local resource which invokes | |||
|  |    * OMB_Consumer::finishAuthorization on request. | |||
|  |    * | |||
|  |    * @param OMB_Profile $profile    An OMB_Profile object representing the | |||
|  |    *                                soon-to-be subscribed (i. e. local) user | |||
|  |    * @param string      $finish_url Target location after successful | |||
|  |    *                                authorization | |||
|  |    * | |||
|  |    * @access public | |||
|  |    * | |||
|  |    * @return string An URL representing an authorization request | |||
|  |    **/ | |||
|  |   public function requestAuthorization($profile, $finish_url) { | |||
|  |     if ($this->performLegacyAuthRequest) { | |||
|  |       $params = $profile->asParameters('omb_listenee', false); | |||
|  |       $params['omb_listener'] = $this->listener_uri; | |||
|  |       $params['oauth_callback'] = $finish_url; | |||
|  | 
 | |||
|  |       $url = $this->prepareAction(OAUTH_ENDPOINT_AUTHORIZE, $params, 'GET')->to_url(); | |||
|  |     } else { | |||
|  | 
 | |||
|  |       $params = array( | |||
|  |                 'oauth_callback' => $finish_url, | |||
|  |                 'oauth_token'    => $this->token->key, | |||
|  |                 'omb_version'    => OMB_VERSION, | |||
|  |                 'omb_listener'   => $this->listener_uri); | |||
|  | 
 | |||
|  |       $params = array_merge($profile->asParameters('omb_listenee', false). $params); | |||
|  | 
 | |||
|  |       /* Build result URL. */ | |||
|  |       $url = $this->services[OAUTH_ENDPOINT_AUTHORIZE]; | |||
|  |       $url .= (strrpos($url, '?') === false ? '?' : '&'); | |||
|  |       foreach ($params as $k => $v) { | |||
|  |         $url .= OAuthUtil::urlencode_rfc3986($k) . '=' . OAuthUtil::urlencode_rfc3986($v) . '&'; | |||
|  |       } | |||
|  |     } | |||
|  | 
 | |||
|  |     $this->listenee_uri = $profile->getIdentifierURI(); | |||
|  | 
 | |||
|  |     return $url; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Finish authorization | |||
|  |    * | |||
|  |    * Finish the subscription process by converting the received and authorized | |||
|  |    * request token into an access token. After that, the subscriber’s profile | |||
|  |    * and the subscription are stored in the database. | |||
|  |    * Expects an OAuthRequest in query parameters. | |||
|  |    * Throws exceptions on failure. | |||
|  |    * | |||
|  |    * @access public | |||
|  |    **/ | |||
|  |   public function finishAuthorization() { | |||
|  |     OMB_Helper::removeMagicQuotesFromRequest(); | |||
|  |     $req = OAuthRequest::from_request(); | |||
|  |     if ($req->get_parameter('oauth_token') != | |||
|  |           $this->token->key) { | |||
|  |       /* That’s not the token I wanted to get authorized. */ | |||
|  |       throw new OAuthException('The authorized token does not equal the ' . | |||
|  |                                'submitted token.'); | |||
|  |     } | |||
|  | 
 | |||
|  |     if ($req->get_parameter('omb_version') != OMB_VERSION) { | |||
|  |       throw new OMB_RemoteServiceException('The remote service uses an ' . | |||
|  |                                            'unsupported OMB version'); | |||
|  |     } | |||
|  | 
 | |||
|  |     /* Construct the profile to validate it. */ | |||
|  | 
 | |||
|  |     /* Fix OMB bug. Listener URI is not passed. */ | |||
|  |     if ($_SERVER['REQUEST_METHOD'] == 'POST') { | |||
|  |       $params = $_POST; | |||
|  |     } else { | |||
|  |       $params = $_GET; | |||
|  |     } | |||
|  |     $params['omb_listener'] = $this->listener_uri; | |||
|  | 
 | |||
|  |     require_once 'profile.php'; | |||
|  |     $listener = OMB_Profile::fromParameters($params, 'omb_listener'); | |||
|  | 
 | |||
|  |     /* Ask the remote service to convert the authorized request token into an | |||
|  |        access token. */ | |||
|  | 
 | |||
|  |     $result = $this->performAction(OAUTH_ENDPOINT_ACCESS, array()); | |||
|  |     if ($result->status != 200) { | |||
|  |       throw new OAuthException('Could not get access token'); | |||
|  |     } | |||
|  | 
 | |||
|  |     parse_str($result->body, $return); | |||
|  |     if (!isset($return['oauth_token']) || !isset($return['oauth_token_secret'])) { | |||
|  |       throw new OAuthException('Could not get access token'); | |||
|  |     } | |||
|  |     $this->setToken($return['oauth_token'], $return['oauth_token_secret']); | |||
|  | 
 | |||
|  |     /* Subscription is finished and valid. Now store the new subscriber and the | |||
|  |        subscription in the database. */ | |||
|  | 
 | |||
|  |     $this->datastore->saveProfile($listener); | |||
|  |     $this->datastore->saveSubscription($this->listener_uri, | |||
|  |                                        $this->listenee_uri, | |||
|  |                                        $this->token); | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Return the URI identifying the listener | |||
|  |    * | |||
|  |    * Returns the URI for the OMB user who tries to subscribe or already has | |||
|  |    * subscribed our user. This method is a workaround for a serious OMB flaw: | |||
|  |    * The Listener URI is not passed in the finishauthorization call. | |||
|  |    * | |||
|  |    * @access public | |||
|  |    * | |||
|  |    * @return string the listener’s URI | |||
|  |    **/ | |||
|  |   public function getListenerURI() { | |||
|  |     return $this->listener_uri; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Inform the service about a profile update | |||
|  |    * | |||
|  |    * Sends an updated profile to the service. | |||
|  |    * | |||
|  |    * @param OMB_Profile $profile The profile that has changed | |||
|  |    * | |||
|  |    * @access public | |||
|  |    **/ | |||
|  |   public function updateProfile($profile) { | |||
|  |     $params = $profile->asParameters('omb_listenee', true); | |||
|  |     $this->performOMBAction(OMB_ENDPOINT_UPDATEPROFILE, $params, $profile->getIdentifierURI()); | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Inform the service about a new notice | |||
|  |    * | |||
|  |    * Sends a notice to the service. | |||
|  |    * | |||
|  |    * @param OMB_Notice $notice The notice | |||
|  |    * | |||
|  |    * @access public | |||
|  |    **/ | |||
|  |   public function postNotice($notice) { | |||
|  |     $params = $notice->asParameters(); | |||
|  |     $params['omb_listenee'] = $notice->getAuthor()->getIdentifierURI(); | |||
|  |     $this->performOMBAction(OMB_ENDPOINT_POSTNOTICE, $params, $params['omb_listenee']); | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Set the token member variable | |||
|  |    * | |||
|  |    * Initializes the token based on given token and secret token. | |||
|  |    * | |||
|  |    * @param string $token  The token | |||
|  |    * @param string $secret The secret token | |||
|  |    * | |||
|  |    * @access public | |||
|  |    **/ | |||
|  |   public function setToken($token, $secret) { | |||
|  |     $this->token = new OAuthToken($token, $secret); | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Prepare an OAuthRequest object | |||
|  |    * | |||
|  |    * Creates an OAuthRequest object mapping the request specified by the | |||
|  |    * parameters. | |||
|  |    * | |||
|  |    * @param string $action_uri The URI specifying the target service | |||
|  |    * @param array  $params     Additional parameters for the service call | |||
|  |    * @param string $method     The HTTP method used to call the service | |||
|  |    *                           ('POST' or 'GET', usually) | |||
|  |    * | |||
|  |    * @access protected | |||
|  |    * | |||
|  |    * @return OAuthRequest the prepared request | |||
|  |    **/ | |||
|  |   protected function prepareAction($action_uri, $params, $method) { | |||
|  |     $url = $this->services[$action_uri]; | |||
|  | 
 | |||
|  |     $url_params = array(); | |||
|  |     parse_str(parse_url($url, PHP_URL_QUERY), $url_params); | |||
|  | 
 | |||
|  |     /* Add OMB version. */ | |||
|  |     $url_params['omb_version'] = OMB_VERSION; | |||
|  | 
 | |||
|  |     /* Add user-defined parameters. */ | |||
|  |     $url_params = array_merge($url_params, $params); | |||
|  | 
 | |||
|  |     $req = OAuthRequest::from_consumer_and_token($this->oauth_consumer, | |||
|  |                                       $this->token, $method, $url, $url_params); | |||
|  | 
 | |||
|  |     /* Sign the request. */ | |||
|  |     $req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(), | |||
|  |                                            $this->oauth_consumer, $this->token); | |||
|  | 
 | |||
|  |     return $req; | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Perform a service call | |||
|  |    * | |||
|  |    * Creates an OAuthRequest object and execute the mapped call as POST request. | |||
|  |    * | |||
|  |    * @param string $action_uri The URI specifying the target service | |||
|  |    * @param array  $params     Additional parameters for the service call | |||
|  |    * | |||
|  |    * @access protected | |||
|  |    * | |||
|  |    * @return Auth_Yadis_HTTPResponse The POST request response | |||
|  |    **/ | |||
|  |   protected function performAction($action_uri, $params) { | |||
|  |     $req = $this->prepareAction($action_uri, $params, 'POST'); | |||
|  | 
 | |||
|  |     /* Return result page. */ | |||
|  |     return $this->fetcher->post($req->get_normalized_http_url(), $req->to_postdata(), array()); | |||
|  |   } | |||
|  | 
 | |||
|  |   /** | |||
|  |    * Perform an OMB action | |||
|  |    * | |||
|  |    * Executes an OMB action – to date, it’s one of updateProfile or postNotice. | |||
|  |    * | |||
|  |    * @param string $action_uri   The URI specifying the target service | |||
|  |    * @param array  $params       Additional parameters for the service call | |||
|  |    * @param string $listenee_uri The URI identifying the local user for whom | |||
|  |    *                             the action is performed | |||
|  |    * | |||
|  |    * @access protected | |||
|  |    **/ | |||
|  |   protected function performOMBAction($action_uri, $params, $listenee_uri) { | |||
|  |     $result = $this->performAction($action_uri, $params); | |||
|  |     if ($result->status == 403) { | |||
|  |       /* The remote user unsubscribed us. */ | |||
|  |       $this->datastore->deleteSubscription($this->listener_uri, $listenee_uri); | |||
|  |     } else if ($result->status != 200 || | |||
|  |                strpos($result->body, 'omb_version=' . OMB_VERSION) === false) { | |||
|  |       /* The server signaled an error or sent an incorrect response. */ | |||
|  |       throw OMB_RemoteServiceException::fromYadis($action_uri, $result); | |||
|  |     } | |||
|  |   } | |||
|  | } |