import v1.1.0_beta1 | 2009-08-21
This commit is contained in:
548
libs/Auth/OpenID/Discover.php
Normal file
548
libs/Auth/OpenID/Discover.php
Normal file
@ -0,0 +1,548 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
?>
|
Reference in New Issue
Block a user