summaryrefslogtreecommitdiff
path: root/extlib/Auth/OpenID/Discover.php
diff options
context:
space:
mode:
Diffstat (limited to 'extlib/Auth/OpenID/Discover.php')
-rw-r--r--extlib/Auth/OpenID/Discover.php548
1 files changed, 548 insertions, 0 deletions
diff --git a/extlib/Auth/OpenID/Discover.php b/extlib/Auth/OpenID/Discover.php
new file mode 100644
index 000000000..62aeb1d2b
--- /dev/null
+++ b/extlib/Auth/OpenID/Discover.php
@@ -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;
+}
+
+?>