forked from GNUsocial/gnu-social
		
	
		
			
				
	
	
		
			549 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			549 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * The OpenID and Yadis discovery implementation for OpenID 1.2.
 | |
|  */
 | |
| 
 | |
| require_once "Auth/OpenID.php";
 | |
| require_once "Auth/OpenID/Parse.php";
 | |
| require_once "Auth/OpenID/Message.php";
 | |
| require_once "Auth/Yadis/XRIRes.php";
 | |
| require_once "Auth/Yadis/Yadis.php";
 | |
| 
 | |
| // XML namespace value
 | |
| define('Auth_OpenID_XMLNS_1_0', 'http://openid.net/xmlns/1.0');
 | |
| 
 | |
| // Yadis service types
 | |
| define('Auth_OpenID_TYPE_1_2', 'http://openid.net/signon/1.2');
 | |
| define('Auth_OpenID_TYPE_1_1', 'http://openid.net/signon/1.1');
 | |
| define('Auth_OpenID_TYPE_1_0', 'http://openid.net/signon/1.0');
 | |
| define('Auth_OpenID_TYPE_2_0_IDP', 'http://specs.openid.net/auth/2.0/server');
 | |
| define('Auth_OpenID_TYPE_2_0', 'http://specs.openid.net/auth/2.0/signon');
 | |
| define('Auth_OpenID_RP_RETURN_TO_URL_TYPE',
 | |
|        'http://specs.openid.net/auth/2.0/return_to');
 | |
| 
 | |
| function Auth_OpenID_getOpenIDTypeURIs()
 | |
| {
 | |
|     return array(Auth_OpenID_TYPE_2_0_IDP,
 | |
|                  Auth_OpenID_TYPE_2_0,
 | |
|                  Auth_OpenID_TYPE_1_2,
 | |
|                  Auth_OpenID_TYPE_1_1,
 | |
|                  Auth_OpenID_TYPE_1_0,
 | |
|                  Auth_OpenID_RP_RETURN_TO_URL_TYPE);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Object representing an OpenID service endpoint.
 | |
|  */
 | |
| class Auth_OpenID_ServiceEndpoint {
 | |
|     function Auth_OpenID_ServiceEndpoint()
 | |
|     {
 | |
|         $this->claimed_id = null;
 | |
|         $this->server_url = null;
 | |
|         $this->type_uris = array();
 | |
|         $this->local_id = null;
 | |
|         $this->canonicalID = null;
 | |
|         $this->used_yadis = false; // whether this came from an XRDS
 | |
|         $this->display_identifier = null;
 | |
|     }
 | |
| 
 | |
|     function getDisplayIdentifier()
 | |
|     {
 | |
|         if ($this->display_identifier) {
 | |
|             return $this->display_identifier;
 | |
|         }
 | |
|         if (! $this->claimed_id) {
 | |
|           return $this->claimed_id;
 | |
|         }
 | |
|         $parsed = parse_url($this->claimed_id);
 | |
|         $scheme = $parsed['scheme'];
 | |
|         $host = $parsed['host'];
 | |
|         $path = $parsed['path'];
 | |
|         if (array_key_exists('query', $parsed)) {
 | |
|             $query = $parsed['query'];
 | |
|             $no_frag = "$scheme://$host$path?$query";
 | |
|         } else {
 | |
|             $no_frag = "$scheme://$host$path";
 | |
|         }
 | |
|         return $no_frag;
 | |
|     }
 | |
| 
 | |
|     function usesExtension($extension_uri)
 | |
|     {
 | |
|         return in_array($extension_uri, $this->type_uris);
 | |
|     }
 | |
| 
 | |
|     function preferredNamespace()
 | |
|     {
 | |
|         if (in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris) ||
 | |
|             in_array(Auth_OpenID_TYPE_2_0, $this->type_uris)) {
 | |
|             return Auth_OpenID_OPENID2_NS;
 | |
|         } else {
 | |
|             return Auth_OpenID_OPENID1_NS;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Query this endpoint to see if it has any of the given type
 | |
|      * URIs. This is useful for implementing other endpoint classes
 | |
|      * that e.g. need to check for the presence of multiple versions
 | |
|      * of a single protocol.
 | |
|      *
 | |
|      * @param $type_uris The URIs that you wish to check
 | |
|      *
 | |
|      * @return all types that are in both in type_uris and
 | |
|      * $this->type_uris
 | |
|      */
 | |
|     function matchTypes($type_uris)
 | |
|     {
 | |
|         $result = array();
 | |
|         foreach ($type_uris as $test_uri) {
 | |
|             if ($this->supportsType($test_uri)) {
 | |
|                 $result[] = $test_uri;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $result;
 | |
|     }
 | |
| 
 | |
|     function supportsType($type_uri)
 | |
|     {
 | |
|         // Does this endpoint support this type?
 | |
|         return ((in_array($type_uri, $this->type_uris)) ||
 | |
|                 (($type_uri == Auth_OpenID_TYPE_2_0) &&
 | |
|                  $this->isOPIdentifier()));
 | |
|     }
 | |
| 
 | |
|     function compatibilityMode()
 | |
|     {
 | |
|         return $this->preferredNamespace() != Auth_OpenID_OPENID2_NS;
 | |
|     }
 | |
| 
 | |
|     function isOPIdentifier()
 | |
|     {
 | |
|         return in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris);
 | |
|     }
 | |
| 
 | |
|     function fromOPEndpointURL($op_endpoint_url)
 | |
|     {
 | |
|         // Construct an OP-Identifier OpenIDServiceEndpoint object for
 | |
|         // a given OP Endpoint URL
 | |
|         $obj = new Auth_OpenID_ServiceEndpoint();
 | |
|         $obj->server_url = $op_endpoint_url;
 | |
|         $obj->type_uris = array(Auth_OpenID_TYPE_2_0_IDP);
 | |
|         return $obj;
 | |
|     }
 | |
| 
 | |
|     function parseService($yadis_url, $uri, $type_uris, $service_element)
 | |
|     {
 | |
|         // Set the state of this object based on the contents of the
 | |
|         // service element.  Return true if successful, false if not
 | |
|         // (if findOPLocalIdentifier returns false).
 | |
|         $this->type_uris = $type_uris;
 | |
|         $this->server_url = $uri;
 | |
|         $this->used_yadis = true;
 | |
| 
 | |
|         if (!$this->isOPIdentifier()) {
 | |
|             $this->claimed_id = $yadis_url;
 | |
|             $this->local_id = Auth_OpenID_findOPLocalIdentifier(
 | |
|                                                     $service_element,
 | |
|                                                     $this->type_uris);
 | |
|             if ($this->local_id === false) {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function getLocalID()
 | |
|     {
 | |
|         // Return the identifier that should be sent as the
 | |
|         // openid.identity_url parameter to the server.
 | |
|         if ($this->local_id === null && $this->canonicalID === null) {
 | |
|             return $this->claimed_id;
 | |
|         } else {
 | |
|             if ($this->local_id) {
 | |
|                 return $this->local_id;
 | |
|             } else {
 | |
|                 return $this->canonicalID;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Parse the given document as XRDS looking for OpenID services.
 | |
|      *
 | |
|      * @return array of Auth_OpenID_ServiceEndpoint or null if the
 | |
|      * document cannot be parsed.
 | |
|      */
 | |
|     function fromXRDS($uri, $xrds_text)
 | |
|     {
 | |
|         $xrds =& Auth_Yadis_XRDS::parseXRDS($xrds_text);
 | |
| 
 | |
|         if ($xrds) {
 | |
|             $yadis_services =
 | |
|               $xrds->services(array('filter_MatchesAnyOpenIDType'));
 | |
|             return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services);
 | |
|         }
 | |
| 
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Create endpoints from a DiscoveryResult.
 | |
|      *
 | |
|      * @param discoveryResult Auth_Yadis_DiscoveryResult
 | |
|      * @return array of Auth_OpenID_ServiceEndpoint or null if
 | |
|      * endpoints cannot be created.
 | |
|      */
 | |
|     function fromDiscoveryResult($discoveryResult)
 | |
|     {
 | |
|         if ($discoveryResult->isXRDS()) {
 | |
|             return Auth_OpenID_ServiceEndpoint::fromXRDS(
 | |
|                                      $discoveryResult->normalized_uri,
 | |
|                                      $discoveryResult->response_text);
 | |
|         } else {
 | |
|             return Auth_OpenID_ServiceEndpoint::fromHTML(
 | |
|                                      $discoveryResult->normalized_uri,
 | |
|                                      $discoveryResult->response_text);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function fromHTML($uri, $html)
 | |
|     {
 | |
|         $discovery_types = array(
 | |
|                                  array(Auth_OpenID_TYPE_2_0,
 | |
|                                        'openid2.provider', 'openid2.local_id'),
 | |
|                                  array(Auth_OpenID_TYPE_1_1,
 | |
|                                        'openid.server', 'openid.delegate')
 | |
|                                  );
 | |
| 
 | |
|         $services = array();
 | |
| 
 | |
|         foreach ($discovery_types as $triple) {
 | |
|             list($type_uri, $server_rel, $delegate_rel) = $triple;
 | |
| 
 | |
|             $urls = Auth_OpenID_legacy_discover($html, $server_rel,
 | |
|                                                 $delegate_rel);
 | |
| 
 | |
|             if ($urls === false) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             list($delegate_url, $server_url) = $urls;
 | |
| 
 | |
|             $service = new Auth_OpenID_ServiceEndpoint();
 | |
|             $service->claimed_id = $uri;
 | |
|             $service->local_id = $delegate_url;
 | |
|             $service->server_url = $server_url;
 | |
|             $service->type_uris = array($type_uri);
 | |
| 
 | |
|             $services[] = $service;
 | |
|         }
 | |
| 
 | |
|         return $services;
 | |
|     }
 | |
| 
 | |
|     function copy()
 | |
|     {
 | |
|         $x = new Auth_OpenID_ServiceEndpoint();
 | |
| 
 | |
|         $x->claimed_id = $this->claimed_id;
 | |
|         $x->server_url = $this->server_url;
 | |
|         $x->type_uris = $this->type_uris;
 | |
|         $x->local_id = $this->local_id;
 | |
|         $x->canonicalID = $this->canonicalID;
 | |
|         $x->used_yadis = $this->used_yadis;
 | |
| 
 | |
|         return $x;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_findOPLocalIdentifier($service, $type_uris)
 | |
| {
 | |
|     // Extract a openid:Delegate value from a Yadis Service element.
 | |
|     // If no delegate is found, returns null.  Returns false on
 | |
|     // discovery failure (when multiple delegate/localID tags have
 | |
|     // different values).
 | |
| 
 | |
|     $service->parser->registerNamespace('openid',
 | |
|                                         Auth_OpenID_XMLNS_1_0);
 | |
| 
 | |
|     $service->parser->registerNamespace('xrd',
 | |
|                                         Auth_Yadis_XMLNS_XRD_2_0);
 | |
| 
 | |
|     $parser =& $service->parser;
 | |
| 
 | |
|     $permitted_tags = array();
 | |
| 
 | |
|     if (in_array(Auth_OpenID_TYPE_1_1, $type_uris) ||
 | |
|         in_array(Auth_OpenID_TYPE_1_0, $type_uris)) {
 | |
|         $permitted_tags[] = 'openid:Delegate';
 | |
|     }
 | |
| 
 | |
|     if (in_array(Auth_OpenID_TYPE_2_0, $type_uris)) {
 | |
|         $permitted_tags[] = 'xrd:LocalID';
 | |
|     }
 | |
| 
 | |
|     $local_id = null;
 | |
| 
 | |
|     foreach ($permitted_tags as $tag_name) {
 | |
|         $tags = $service->getElements($tag_name);
 | |
| 
 | |
|         foreach ($tags as $tag) {
 | |
|             $content = $parser->content($tag);
 | |
| 
 | |
|             if ($local_id === null) {
 | |
|                 $local_id = $content;
 | |
|             } else if ($local_id != $content) {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return $local_id;
 | |
| }
 | |
| 
 | |
| function filter_MatchesAnyOpenIDType(&$service)
 | |
| {
 | |
|     $uris = $service->getTypes();
 | |
| 
 | |
|     foreach ($uris as $uri) {
 | |
|         if (in_array($uri, Auth_OpenID_getOpenIDTypeURIs())) {
 | |
|             return true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_bestMatchingService($service, $preferred_types)
 | |
| {
 | |
|     // Return the index of the first matching type, or something
 | |
|     // higher if no type matches.
 | |
|     //
 | |
|     // This provides an ordering in which service elements that
 | |
|     // contain a type that comes earlier in the preferred types list
 | |
|     // come before service elements that come later. If a service
 | |
|     // element has more than one type, the most preferred one wins.
 | |
| 
 | |
|     foreach ($preferred_types as $index => $typ) {
 | |
|         if (in_array($typ, $service->type_uris)) {
 | |
|             return $index;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return count($preferred_types);
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_arrangeByType($service_list, $preferred_types)
 | |
| {
 | |
|     // Rearrange service_list in a new list so services are ordered by
 | |
|     // types listed in preferred_types.  Return the new list.
 | |
| 
 | |
|     // Build a list with the service elements in tuples whose
 | |
|     // comparison will prefer the one with the best matching service
 | |
|     $prio_services = array();
 | |
|     foreach ($service_list as $index => $service) {
 | |
|         $prio_services[] = array(Auth_OpenID_bestMatchingService($service,
 | |
|                                                         $preferred_types),
 | |
|                                  $index, $service);
 | |
|     }
 | |
| 
 | |
|     sort($prio_services);
 | |
| 
 | |
|     // Now that the services are sorted by priority, remove the sort
 | |
|     // keys from the list.
 | |
|     foreach ($prio_services as $index => $s) {
 | |
|         $prio_services[$index] = $prio_services[$index][2];
 | |
|     }
 | |
| 
 | |
|     return $prio_services;
 | |
| }
 | |
| 
 | |
| // Extract OP Identifier services.  If none found, return the rest,
 | |
| // sorted with most preferred first according to
 | |
| // OpenIDServiceEndpoint.openid_type_uris.
 | |
| //
 | |
| // openid_services is a list of OpenIDServiceEndpoint objects.
 | |
| //
 | |
| // Returns a list of OpenIDServiceEndpoint objects."""
 | |
| function Auth_OpenID_getOPOrUserServices($openid_services)
 | |
| {
 | |
|     $op_services = Auth_OpenID_arrangeByType($openid_services,
 | |
|                                      array(Auth_OpenID_TYPE_2_0_IDP));
 | |
| 
 | |
|     $openid_services = Auth_OpenID_arrangeByType($openid_services,
 | |
|                                      Auth_OpenID_getOpenIDTypeURIs());
 | |
| 
 | |
|     if ($op_services) {
 | |
|         return $op_services;
 | |
|     } else {
 | |
|         return $openid_services;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services)
 | |
| {
 | |
|     $s = array();
 | |
| 
 | |
|     if (!$yadis_services) {
 | |
|         return $s;
 | |
|     }
 | |
| 
 | |
|     foreach ($yadis_services as $service) {
 | |
|         $type_uris = $service->getTypes();
 | |
|         $uris = $service->getURIs();
 | |
| 
 | |
|         // If any Type URIs match and there is an endpoint URI
 | |
|         // specified, then this is an OpenID endpoint
 | |
|         if ($type_uris &&
 | |
|             $uris) {
 | |
|             foreach ($uris as $service_uri) {
 | |
|                 $openid_endpoint = new Auth_OpenID_ServiceEndpoint();
 | |
|                 if ($openid_endpoint->parseService($uri,
 | |
|                                                    $service_uri,
 | |
|                                                    $type_uris,
 | |
|                                                    $service)) {
 | |
|                     $s[] = $openid_endpoint;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return $s;
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_discoverWithYadis($uri, &$fetcher,
 | |
|               $endpoint_filter='Auth_OpenID_getOPOrUserServices',
 | |
|               $discover_function=null)
 | |
| {
 | |
|     // Discover OpenID services for a URI. Tries Yadis and falls back
 | |
|     // on old-style <link rel='...'> discovery if Yadis fails.
 | |
| 
 | |
|     // Might raise a yadis.discover.DiscoveryFailure if no document
 | |
|     // came back for that URI at all.  I don't think falling back to
 | |
|     // OpenID 1.0 discovery on the same URL will help, so don't bother
 | |
|     // to catch it.
 | |
|     if ($discover_function === null) {
 | |
|         $discover_function = array('Auth_Yadis_Yadis', 'discover');
 | |
|     }
 | |
| 
 | |
|     $openid_services = array();
 | |
| 
 | |
|     $response = call_user_func_array($discover_function,
 | |
|                                      array($uri, &$fetcher));
 | |
| 
 | |
|     $yadis_url = $response->normalized_uri;
 | |
|     $yadis_services = array();
 | |
| 
 | |
|     if ($response->isFailure()) {
 | |
|         return array($uri, array());
 | |
|     }
 | |
| 
 | |
|     $openid_services = Auth_OpenID_ServiceEndpoint::fromXRDS(
 | |
|                                          $yadis_url,
 | |
|                                          $response->response_text);
 | |
| 
 | |
|     if (!$openid_services) {
 | |
|         if ($response->isXRDS()) {
 | |
|             return Auth_OpenID_discoverWithoutYadis($uri,
 | |
|                                                     $fetcher);
 | |
|         }
 | |
| 
 | |
|         // Try to parse the response as HTML to get OpenID 1.0/1.1
 | |
|         // <link rel="...">
 | |
|         $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
 | |
|                                         $yadis_url,
 | |
|                                         $response->response_text);
 | |
|     }
 | |
| 
 | |
|     $openid_services = call_user_func_array($endpoint_filter,
 | |
|                                             array(&$openid_services));
 | |
| 
 | |
|     return array($yadis_url, $openid_services);
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_discoverURI($uri, &$fetcher)
 | |
| {
 | |
|     $uri = Auth_OpenID::normalizeUrl($uri);
 | |
|     return Auth_OpenID_discoverWithYadis($uri, $fetcher);
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_discoverWithoutYadis($uri, &$fetcher)
 | |
| {
 | |
|     $http_resp = @$fetcher->get($uri);
 | |
| 
 | |
|     if ($http_resp->status != 200 and $http_resp->status != 206) {
 | |
|         return array($uri, array());
 | |
|     }
 | |
| 
 | |
|     $identity_url = $http_resp->final_url;
 | |
| 
 | |
|     // Try to parse the response as HTML to get OpenID 1.0/1.1 <link
 | |
|     // rel="...">
 | |
|     $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
 | |
|                                            $identity_url,
 | |
|                                            $http_resp->body);
 | |
| 
 | |
|     return array($identity_url, $openid_services);
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_discoverXRI($iname, &$fetcher)
 | |
| {
 | |
|     $resolver = new Auth_Yadis_ProxyResolver($fetcher);
 | |
|     list($canonicalID, $yadis_services) =
 | |
|         $resolver->query($iname,
 | |
|                          Auth_OpenID_getOpenIDTypeURIs(),
 | |
|                          array('filter_MatchesAnyOpenIDType'));
 | |
| 
 | |
|     $openid_services = Auth_OpenID_makeOpenIDEndpoints($iname,
 | |
|                                                        $yadis_services);
 | |
| 
 | |
|     $openid_services = Auth_OpenID_getOPOrUserServices($openid_services);
 | |
| 
 | |
|     for ($i = 0; $i < count($openid_services); $i++) {
 | |
|         $openid_services[$i]->canonicalID = $canonicalID;
 | |
|         $openid_services[$i]->claimed_id = $canonicalID;
 | |
|         $openid_services[$i]->display_identifier = $iname;
 | |
|     }
 | |
| 
 | |
|     // FIXME: returned xri should probably be in some normal form
 | |
|     return array($iname, $openid_services);
 | |
| }
 | |
| 
 | |
| function Auth_OpenID_discover($uri, &$fetcher)
 | |
| {
 | |
|     // If the fetcher (i.e., PHP) doesn't support SSL, we can't do
 | |
|     // discovery on an HTTPS URL.
 | |
|     if ($fetcher->isHTTPS($uri) && !$fetcher->supportsSSL()) {
 | |
|         return array($uri, array());
 | |
|     }
 | |
| 
 | |
|     if (Auth_Yadis_identifierScheme($uri) == 'XRI') {
 | |
|         $result = Auth_OpenID_discoverXRI($uri, $fetcher);
 | |
|     } else {
 | |
|         $result = Auth_OpenID_discoverURI($uri, $fetcher);
 | |
|     }
 | |
| 
 | |
|     // If the fetcher doesn't support SSL, we can't interact with
 | |
|     // HTTPS server URLs; remove those endpoints from the list.
 | |
|     if (!$fetcher->supportsSSL()) {
 | |
|         $http_endpoints = array();
 | |
|         list($new_uri, $endpoints) = $result;
 | |
| 
 | |
|         foreach ($endpoints as $e) {
 | |
|             if (!$fetcher->isHTTPS($e->server_url)) {
 | |
|                 $http_endpoints[] = $e;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $result = array($new_uri, $http_endpoints);
 | |
|     }
 | |
| 
 | |
|     return $result;
 | |
| }
 | |
| 
 | |
| ?>
 |