diff options
author | Dries Buytaert <dries@buytaert.net> | 2010-03-22 18:48:20 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2010-03-22 18:48:20 +0000 |
commit | e8d18e41fc25af8dad5746e5b3bd6982c2492c2e (patch) | |
tree | ea2f2bf94b63828003376e9d4ad3c5e18a27e474 /modules/openid | |
parent | 2598778efa1887a4f2a8eb016114d32e8b6259a8 (diff) | |
download | brdo-e8d18e41fc25af8dad5746e5b3bd6982c2492c2e.tar.gz brdo-e8d18e41fc25af8dad5746e5b3bd6982c2492c2e.tar.bz2 |
- Patch #218097 by c960657: OpenID must use canonical ID when authenticating XRI i-names.
Diffstat (limited to 'modules/openid')
-rw-r--r-- | modules/openid/openid.inc | 59 | ||||
-rw-r--r-- | modules/openid/openid.module | 53 | ||||
-rw-r--r-- | modules/openid/openid.test | 3 | ||||
-rw-r--r-- | modules/openid/tests/openid_test.module | 21 | ||||
-rw-r--r-- | modules/openid/xrds.inc | 90 |
5 files changed, 114 insertions, 112 deletions
diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc index a1f3b90b4..72e931718 100644 --- a/modules/openid/openid.inc +++ b/modules/openid/openid.inc @@ -61,6 +61,16 @@ define('OPENID_NS_SREG', 'http://openid.net/extensions/sreg/1.1'); define('OPENID_NS_AX', 'http://openid.net/srv/ax/1.0'); /** + * Extensible Resource Descriptor documents. + */ +define('OPENID_NS_XRD', 'xri://$xrd*($v*2.0)'); + +/** + * OpenID IDP for Google hosted domains. + */ +define('OPENID_NS_GOOGLE', 'http://namespace.google.com/openid/xmlns'); + +/** * Performs an HTTP 302 redirect (for the 1.x protocol). */ function openid_redirect_http($url, $message) { @@ -109,6 +119,49 @@ function openid_redirect_form($form, &$form_state, $url, $message) { } /** + * Parse an XRDS document. + * + * @param $raw_xml + * A string containing the XRDS document. + * @return + * An array of service entries. + */ +function _openid_xrds_parse($raw_xml) { + $services = array(); + try { + $xml = @new SimpleXMLElement($raw_xml); + foreach ($xml->children(OPENID_NS_XRD)->XRD as $xrd) { + foreach ($xrd->children(OPENID_NS_XRD)->Service as $service_element) { + $service = array( + 'priority' => $service_element->attributes()->priority ? (int)$service_element->attributes()->priority : PHP_INT_MAX, + 'types' => array(), + 'uri' => (string)$service_element->children(OPENID_NS_XRD)->URI, + 'service' => $service_element, + 'xrd' => $xrd, + ); + foreach ($service_element->Type as $type) { + $service['types'][] = (string)$type; + } + if ($service_element->children(OPENID_NS_XRD)->Delegate) { + $service['identity'] = (string)$service_element->children(OPENID_NS_XRD)->Delegate; + } + if ($service_element->children(OPENID_NS_XRD)->LocalID) { + $service['identity'] = (string)$service_element->children(OPENID_NS_XRD)->LocalID; + } + else { + $service['identity'] = FALSE; + } + $services[] = $service; + } + } + } + catch (Exception $e) { + // Invalid XML. + } + return $services; +} + +/** * Select a service element. * * The procedure is described in OpenID Authentication 2.0, section 7.3.2. @@ -155,6 +208,12 @@ function _openid_select_service(array $services) { } } + if ($selected_service) { + // Unset SimpleXMLElement instances that cannot be saved in $_SESSION. + unset($selected_service['xrd']); + unset($selected_service['service']); + } + return $selected_service; } diff --git a/modules/openid/openid.module b/modules/openid/openid.module index e270006a2..6d8cbfbdf 100644 --- a/modules/openid/openid.module +++ b/modules/openid/openid.module @@ -208,16 +208,12 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) { $claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select'; } else { - // Look for OP-Local Identifier. - if (!empty($service['localid'])) { - $identity = $service['localid']; - } - elseif (!empty($service['delegate'])) { - $identity = $service['delegate']; - } - else { - $identity = $claimed_id; + // Use Claimed ID and/or OP-Local Identifier from service description, if + // available. + if (!empty($service['claimed_id'])) { + $claimed_id = $service['claimed_id']; } + $identity = !empty($service['identity']) ? $service['identity'] : $claimed_id; } $request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $service); @@ -258,12 +254,21 @@ function openid_complete($response = array()) { } else { if (openid_verify_assertion($service['uri'], $response)) { + // OpenID Authentication, section 7.3.2.3 and Appendix A.5: + // The CanonicalID specified in the XRDS document must be used as the + // account key. We rely on the XRI proxy resolver to verify that the + // provider is authorized to respond on behalf of the specified + // identifer (required per Extensible Resource Identifier (XRI) + // (XRI) Resolution Version 2.0, section 14.3): + if (!empty($service['claimed_id'])) { + $response['openid.claimed_id'] = $service['claimed_id']; + } // OpenID Authentication, section 11.2: // If the returned Claimed Identifier is different from the one sent // to the OpenID Provider, we need to do discovery on the returned - // identififer to make sure that the provider is authorized to respond + // identifier to make sure that the provider is authorized to respond // on behalf of this. - if ($service['version'] == 2 && $response['openid.claimed_id'] != openid_normalize($claimed_id)) { + elseif ($service['version'] == 2 && $response['openid.claimed_id'] != openid_normalize($claimed_id)) { $services = openid_discovery($response['openid.claimed_id']); $uris = array(); foreach ($services as $discovered_service) { @@ -338,8 +343,14 @@ function openid_openid_discovery_method_info() { */ function _openid_xri_discovery($claimed_id) { if (_openid_is_xri($claimed_id)) { - $xrds_url = 'http://xri.net/' . $claimed_id; - _openid_xrds_discovery($xrds_url); + // Resolve XRI using a proxy resolver (Extensible Resource Identifier (XRI) + // Resolution Version 2.0, section 11.2). + $xrds_url = variable_get('xri_proxy_resolver', 'http://xri.net/') . rawurlencode($claimed_id) . '?_xrd_r=application/xrds+xml'; + $services = _openid_xrds_discovery($xrds_url); + foreach ($services as &$service) { + $service['claimed_id'] = openid_normalize((string)$service['xrd']->children(OPENID_NS_XRD)->CanonicalID); + } + return $services; } } @@ -362,7 +373,7 @@ function _openid_xrds_discovery($claimed_id) { if (!isset($result->error)) { if (isset($result->headers['Content-Type']) && preg_match("/application\/xrds\+xml/", $result->headers['Content-Type'])) { // Parse XML document to find URL - $services = xrds_parse($result->data); + $services = _openid_xrds_parse($result->data); } else { $xrds_url = NULL; @@ -377,7 +388,7 @@ function _openid_xrds_discovery($claimed_id) { $headers = array('Accept' => 'application/xrds+xml'); $xrds_result = drupal_http_request($xrds_url, array('headers' => $headers)); if (!isset($xrds_result->error)) { - $services = xrds_parse($xrds_result->data); + $services = _openid_xrds_parse($xrds_result->data); } } } @@ -386,19 +397,19 @@ function _openid_xrds_discovery($claimed_id) { if (count($services) == 0) { // Look for 2.0 links $uri = _openid_link_href('openid2.provider', $result->data); - $delegate = _openid_link_href('openid2.local_id', $result->data); + $identity = _openid_link_href('openid2.local_id', $result->data); $type = 'http://specs.openid.net/auth/2.0/signon'; // 1.x links if (empty($uri)) { $uri = _openid_link_href('openid.server', $result->data); - $delegate = _openid_link_href('openid.delegate', $result->data); + $identity = _openid_link_href('openid.delegate', $result->data); $type = 'http://openid.net/signon/1.1'; } if (!empty($uri)) { $services[] = array( 'uri' => $uri, - 'delegate' => $delegate, + 'identity' => $identity, 'types' => array($type), ); } @@ -432,9 +443,9 @@ function _openid_google_user_discovery($claimed_id) { $xrds_url = $matches[1]; $services = _openid_xrds_discovery($xrds_url); - foreach ($services as $service) { - if (in_array('http://www.iana.org/assignments/relation/describedby', $service['types']) && !empty($service['additional']['URITEMPLATE'])) { - $template = $service['additional']['URITEMPLATE']; + foreach ($services as $i => $service) { + if (in_array('http://www.iana.org/assignments/relation/describedby', $service['types']) && $service['service']->children(OPENID_NS_GOOGLE)->URITemplate) { + $template = (string)$service['service']->children(OPENID_NS_GOOGLE)->URITemplate; $xrds_url = str_replace('{%uri}', rawurlencode($claimed_id), $template); return _openid_xrds_discovery($xrds_url); } diff --git a/modules/openid/openid.test b/modules/openid/openid.test index fc88158a0..7002c7a1f 100644 --- a/modules/openid/openid.test +++ b/modules/openid/openid.test @@ -59,6 +59,9 @@ class OpenIDFunctionalTest extends DrupalWebTestCase { // element that contains the URL of an XRDS document. $this->addIdentity(url('openid-test/yadis/http-equiv', array('absolute' => TRUE)), 2); + // Identifier is an XRI. Resolve using our own dummy proxy resolver. + variable_set('xri_proxy_resolver', url('openid-test/yadis/xrds/xri', array('absolute' => TRUE)) . '/'); + $this->addIdentity('@example*résumé;%25', 2, 'http://example.com/user'); // HTML-based discovery: // If the User-supplied Identifier is a URL of an HTML page, the page may diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module index e980bb6c8..db03641c5 100644 --- a/modules/openid/tests/openid_test.module +++ b/modules/openid/tests/openid_test.module @@ -69,10 +69,29 @@ function openid_test_menu() { */ function openid_test_yadis_xrds() { if ($_SERVER['HTTP_ACCEPT'] == 'application/xrds+xml') { + // Only respond to XRI requests for one specific XRI. The is used to verify + // that the XRI has been properly encoded. The "+" sign in the _xrd_r query + // parameter is decoded to a space by PHP. + if (arg(3) == 'xri') { + if (variable_get('clean_url', 0)) { + if (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml') { + drupal_not_found(); + } + } + else { + // Drupal cannot properly emulate an XRI proxy resolver using unclean + // URLs, so the arguments gets messed up. + if (arg(4) . '/' . arg(5) != '@example*résumé;%25?_xrd_r=application/xrds xml') { + drupal_not_found(); + } + } + } drupal_add_http_header('Content-Type', 'application/xrds+xml'); print '<?xml version="1.0" encoding="UTF-8"?> <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)"> <XRD> + <ProviderID>xri://@</ProviderID> + <CanonicalID>http://example.com/user</CanonicalID> <Service> <Type>http://example.com/this-is-ignored</Type> </Service> @@ -102,7 +121,7 @@ function openid_test_yadis_xrds() { </Service>'; } print ' - <XRD> + </XRD> </xrds:XRDS>'; } else { diff --git a/modules/openid/xrds.inc b/modules/openid/xrds.inc deleted file mode 100644 index 6e05d0c09..000000000 --- a/modules/openid/xrds.inc +++ /dev/null @@ -1,90 +0,0 @@ -<?php -// $Id$ - -// Global variables to track parsing state -$xrds_open_elements = array(); -$xrds_services = array(); -$xrds_current_service = array(); - -/** - * Main entry point for parsing XRDS documents - */ -function xrds_parse($xml) { - global $xrds_services; - - $parser = xml_parser_create_ns(); - xml_set_element_handler($parser, '_xrds_element_start', '_xrds_element_end'); - xml_set_character_data_handler($parser, '_xrds_cdata'); - - xml_parse($parser, $xml); - xml_parser_free($parser); - - return $xrds_services; -} - -/** - * Parser callback functions - */ -function _xrds_element_start(&$parser, $name, $attributes) { - global $xrds_open_elements, $xrds_current_service; - - $xrds_open_elements[] = _xrds_strip_namespace($name); - - $path = strtoupper(implode('/', $xrds_open_elements)); - if ($path == 'XRDS/XRD/SERVICE') { - foreach ($attributes as $attribute_name => $value) { - if (_xrds_strip_namespace($attribute_name) == 'PRIORITY') { - $xrds_current_service['priority'] = intval($value); - } - } - } -} - -function _xrds_element_end(&$parser, $name) { - global $xrds_open_elements, $xrds_services, $xrds_current_service; - - $name = _xrds_strip_namespace($name); - if ($name == 'SERVICE') { - if (!isset($xrds_current_service['priority'])) { - // If the priority attribute is absent, the default is infinity. - $xrds_current_service['priority'] = PHP_INT_MAX; - } - $xrds_services[] = $xrds_current_service; - $xrds_current_service = array(); - } - array_pop($xrds_open_elements); -} - -function _xrds_cdata(&$parser, $data) { - global $xrds_open_elements, $xrds_services, $xrds_current_service; - $path = strtoupper(implode('/', $xrds_open_elements)); - switch ($path) { - case 'XRDS/XRD/SERVICE/TYPE': - $xrds_current_service['types'][] = $data; - break; - case 'XRDS/XRD/SERVICE/URI': - $xrds_current_service['uri'] = $data; - break; - case 'XRDS/XRD/SERVICE/DELEGATE': - $xrds_current_service['delegate'] = $data; - break; - case 'XRDS/XRD/SERVICE/LOCALID': - $xrds_current_service['localid'] = $data; - break; - default: - if (preg_match('@^XRDS/XRD/SERVICE/(.*)$@', $path, $matches)) { - $xrds_current_service['additional'][$matches[1]] = $data; - } - break; - } -} - -function _xrds_strip_namespace($name) { - // Strip namespacing. - $pos = strrpos($name, ':'); - if ($pos !== FALSE) { - $name = substr($name, $pos + 1, strlen($name)); - } - - return $name; -} |