Index: modules/openid/openid.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/openid.inc,v
retrieving revision 1.26
diff -u -9 -p -r1.26 openid.inc
--- modules/openid/openid.inc 6 Mar 2010 06:39:00 -0000 1.26
+++ modules/openid/openid.inc 21 Mar 2010 20:47:47 -0000
@@ -55,18 +55,28 @@ define('OPENID_NS_1_0', 'http://openid.n
*/
define('OPENID_NS_SREG', 'http://openid.net/extensions/sreg/1.1');
/**
* OpenID Attribute Exchange extension.
*/
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) {
$query = array();
foreach ($message as $key => $val) {
$query[] = $key . '=' . urlencode($val);
}
$sep = (strpos($url, '?') === FALSE) ? '?' : '&';
@@ -103,18 +113,61 @@ function openid_redirect_form($form, &$f
'#prefix' => '',
'#value' => t('Send'),
);
return $form;
}
/**
+ * 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.
*
* A new entry is added to the returned array with the key 'version' and the
* value 1 or 2 specifying the protocol version used by the service.
*
* @param $services
* An array of service arrays as returned by openid_discovery().
@@ -149,18 +202,24 @@ function _openid_select_service(array $s
if (!empty($service['uri']) && in_array('http://specs.openid.net/auth/2.0/signon', $service['types'])) {
$service['version'] = 2;
if (!$selected_service || $service['priority'] < $selected_service['priority']) {
$selected_service = $service;
}
}
}
}
+ if ($selected_service) {
+ // Unset SimpleXMLElement instances that cannot be saved in $_SESSION.
+ unset($selected_service['xrd']);
+ unset($selected_service['service']);
+ }
+
return $selected_service;
}
/**
* Determine if the given identifier is an XRI ID.
*/
function _openid_is_xri($identifier) {
// Strip the xri:// scheme from the identifier if present.
if (stripos($identifier, 'xri://') !== FALSE) {
Index: modules/openid/openid.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/openid.module,v
retrieving revision 1.75
diff -u -9 -p -r1.75 openid.module
--- modules/openid/openid.module 9 Mar 2010 12:07:37 -0000 1.75
+++ modules/openid/openid.module 21 Mar 2010 20:47:48 -0000
@@ -202,28 +202,24 @@ function openid_begin($claimed_id, $retu
if (function_exists('bcadd')) {
$assoc_handle = openid_association($service['uri']);
}
if (in_array('http://specs.openid.net/auth/2.0/server', $service['types'])) {
// User entered an OP Identifier.
$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);
if ($service['version'] == 2) {
openid_redirect($service['uri'], $request);
}
else {
openid_redirect_http($service['uri'], $request);
}
@@ -252,24 +248,33 @@ function openid_complete($response = arr
$claimed_id = $_SESSION['openid']['claimed_id'];
unset($_SESSION['openid']['service']);
unset($_SESSION['openid']['claimed_id']);
if (isset($response['openid.mode'])) {
if ($response['openid.mode'] == 'cancel') {
$response['status'] = 'cancel';
}
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) {
if (in_array('http://specs.openid.net/auth/2.0/server', $discovered_service['types']) || in_array('http://specs.openid.net/auth/2.0/signon', $discovered_service['types'])) {
$uris[] = $discovered_service['uri'];
}
}
if (!in_array($service['uri'], $uris)) {
return $response;
@@ -332,20 +337,26 @@ function openid_openid_discovery_method_
/**
* OpenID discovery method: perform an XRI discovery.
*
* @see http://openid.net/specs/openid-authentication-2_0.html#discovery
* @see hook_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;
}
}
/**
* OpenID discovery method: perform a XRDS discovery.
*
* @see http://openid.net/specs/openid-authentication-2_0.html#discovery
* @see hook_openid_discovery_method_info()
*/
@@ -356,55 +367,55 @@ function _openid_xrds_discovery($claimed
$scheme = @parse_url($xrds_url, PHP_URL_SCHEME);
if ($scheme == 'http' || $scheme == 'https') {
// For regular URLs, try Yadis resolution first, then HTML-based discovery
$headers = array('Accept' => 'application/xrds+xml');
$result = drupal_http_request($xrds_url, array('headers' => $headers));
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;
if (isset($result->headers['X-XRDS-Location'])) {
$xrds_url = $result->headers['X-XRDS-Location'];
}
else {
// Look for meta http-equiv link in HTML head
$xrds_url = _openid_meta_httpequiv('X-XRDS-Location', $result->data);
}
if (!empty($xrds_url)) {
$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);
}
}
}
// Check for HTML delegation
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),
);
}
}
}
}
return $services;
}
@@ -426,21 +437,21 @@ function _openid_google_user_discovery($
$response = drupal_http_request('https://www.google.com/accounts/o8/.well-known/host-meta?hd=' . rawurlencode($url['host']));
if (isset($response->error) || $response->code != 200) {
return;
}
if (preg_match('/Link: <(.*)>/', $response->data, $matches)) {
$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);
}
}
}
}
/**
* Implementation of hook_openid_normalization_method_info().
Index: modules/openid/openid.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/openid.test,v
retrieving revision 1.15
diff -u -9 -p -r1.15 openid.test
--- modules/openid/openid.test 2 Mar 2010 08:59:54 -0000 1.15
+++ modules/openid/openid.test 21 Mar 2010 20:47:48 -0000
@@ -53,18 +53,21 @@ class OpenIDFunctionalTest extends Drupa
// Identifier is the URL of an HTML page that is sent with an HTTP header
// that contains the URL of an XRDS document.
$this->addIdentity(url('openid-test/yadis/x-xrds-location', array('absolute' => TRUE)), 2);
// Identifier is the URL of an HTML page containing a
// 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
// contain a element containing the URL of the OpenID
// Provider Endpoint. OpenID 1 and 2 describe slightly different formats.
// OpenID Authentication 1.1, section 3.1:
$this->addIdentity(url('openid-test/html/openid1', array('absolute' => TRUE)), 1);
Index: modules/openid/xrds.inc
===================================================================
RCS file: modules/openid/xrds.inc
diff -N modules/openid/xrds.inc
--- modules/openid/xrds.inc 2 Mar 2010 08:59:54 -0000 1.6
+++ /dev/null 1 Jan 1970 00:00:00 -0000
@@ -1,90 +0,0 @@
- $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;
-}
Index: modules/openid/tests/openid_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/openid/tests/openid_test.module,v
retrieving revision 1.10
diff -u -9 -p -r1.10 openid_test.module
--- modules/openid/tests/openid_test.module 2 Mar 2010 08:55:42 -0000 1.10
+++ modules/openid/tests/openid_test.module 21 Mar 2010 20:47:48 -0000
@@ -63,22 +63,41 @@ function openid_test_menu() {
);
return $items;
}
/**
* Menu callback; XRDS document that references the OP Endpoint URL.
*/
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 '
+ xri://@
+ http://example.com/user
http://example.com/this-is-ignored
http://specs.openid.net/auth/2.0/signon
http://openid.net/srv/ax/1.0
' . url('openid-test/endpoint', array('absolute' => TRUE)) . '
@@ -96,19 +115,19 @@ function openid_test_yadis_xrds() {
http://specs.openid.net/auth/2.0/server
http://example.com/this-has-too-low-priority
http://specs.openid.net/auth/2.0/server
' . url('openid-test/endpoint', array('absolute' => TRUE)) . '
';
}
print '
-
+
';
}
else {
return t('This is a regular HTML page. If the client sends an Accept: application/xrds+xml header when requesting this URL, an XRDS document is returned.');
}
}
/**
* Menu callback; regular HTML page with an X-XRDS-Location HTTP header.