summaryrefslogtreecommitdiff
path: root/modules/openid
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2010-03-02 08:59:54 +0000
committerDries Buytaert <dries@buytaert.net>2010-03-02 08:59:54 +0000
commit0d9eedc8fb26b550c766b432e3e237c563272ace (patch)
tree4ca35341dccaa5c83004333dd65cfb652885b69e /modules/openid
parent094a6b4f85de525d32a0b8787e533e5e77925f7d (diff)
downloadbrdo-0d9eedc8fb26b550c766b432e3e237c563272ace.tar.gz
brdo-0d9eedc8fb26b550c766b432e3e237c563272ace.tar.bz2
- Patch #727650 by Damien Tournoud: made OpenID more pluggable so we can extend it in contrib, and added support for Google's OpenID discovery protocol.
Diffstat (limited to 'modules/openid')
-rw-r--r--modules/openid/openid.api.php60
-rw-r--r--modules/openid/openid.inc67
-rw-r--r--modules/openid/openid.module108
-rw-r--r--modules/openid/openid.pages.inc2
-rw-r--r--modules/openid/openid.test27
-rw-r--r--modules/openid/xrds.inc5
6 files changed, 244 insertions, 25 deletions
diff --git a/modules/openid/openid.api.php b/modules/openid/openid.api.php
index 2144945ee..d71638b45 100644
--- a/modules/openid/openid.api.php
+++ b/modules/openid/openid.api.php
@@ -47,5 +47,65 @@ function hook_openid_response($response, $account) {
}
/**
+ * Allow modules to declare OpenID discovery methods.
+ *
+ * The discovery function callbacks will be called in turn with an unique
+ * parameter, the claimed identifier. They have to return an array of services,
+ * in the same form returned by openid_discover().
+ *
+ * The first discovery method that succeed (return at least one services) will
+ * stop the discovery process.
+ *
+ * @return
+ * An associative array which keys are the name of the discovery methods and
+ * values are function callbacks.
+ * @see hook_openid_discovery_method_info_alter()
+ */
+function hook_openid_discovery_method_info() {
+ return array(
+ 'new_discovery_idea' => '_my_discovery_method',
+ );
+}
+
+/**
+ * Allow modules to alter discovery methods.
+ */
+function hook_openid_discovery_method_info_alter(&$methods) {
+ // Remove Google discovery scheme.
+ unset($methods['google']);
+}
+
+/**
+ * Allow modules to declare OpenID normalization methods.
+ *
+ * The discovery function callbacks will be called in turn with an unique
+ * parameter, the identifier to normalize. They have to return a normalized
+ * identifier, or NULL if the identifier is not in a form they can handle.
+ *
+ * The first normalization method that succeed (return a value that is not NULL)
+ * will stop the normalization process.
+ *
+ * @return
+ * An array with a set of function callbacks, that will be called in turn
+ * when normalizing an OpenID identifier. The normalization functions have
+ * to return a normalized identifier, or NULL if the identifier is not in
+ * a form they can handle.
+ * @see hook_openid_normalization_method_info_alter()
+ */
+function hook_openid_normalization_method_info() {
+ return array(
+ 'new_normalization_idea' => '_my_normalization_method',
+ );
+}
+
+/**
+ * Allow modules to alter normalization methods.
+ */
+function hook_openid_normalization_method_info_alter(&$methods) {
+ // Remove Google IDP normalization.
+ unset($methods['google_idp']);
+}
+
+/**
* @} End of "addtogroup hooks".
*/
diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc
index af40f8eb3..db1a66096 100644
--- a/modules/openid/openid.inc
+++ b/modules/openid/openid.inc
@@ -182,24 +182,39 @@ function _openid_is_xri($identifier) {
*
* The procedure is described in OpenID Authentication 2.0, section 7.2.
*/
-function _openid_normalize($identifier) {
- if (_openid_is_xri($identifier)) {
- return _openid_normalize_xri($identifier);
- }
- else {
- return _openid_normalize_url($identifier);
+function openid_normalize($identifier) {
+ $methods = module_invoke_all('openid_normalization_method_info');
+ drupal_alter('openid_normalization_method_info', $methods);
+
+ // Execute each method in turn, stopping after the first method accepted
+ // the identifier.
+ foreach ($methods as $method) {
+ $result = $method($identifier);
+ if ($result !== NULL) {
+ $identifier = $result;
+ break;
+ }
}
+
+ return $identifier;
}
-function _openid_normalize_xri($xri) {
- $normalized_xri = $xri;
- if (stristr($xri, 'xri://') !== FALSE) {
- $normalized_xri = substr($xri, 6);
+/**
+ * OpenID normalization method: normalize XRI identifiers.
+ */
+function _openid_xri_normalize($identifier) {
+ if (_openid_is_xri($identifier)) {
+ if (stristr($identifier, 'xri://') !== FALSE) {
+ $identifier = substr($identifier, 6);
+ }
+ return $identifier;
}
- return $normalized_xri;
}
-function _openid_normalize_url($url) {
+/**
+ * OpenID normalization method: normalize URL identifiers.
+ */
+function _openid_url_normalize($url) {
$normalized_url = $url;
if (stristr($url, '://') === FALSE) {
@@ -217,6 +232,34 @@ function _openid_normalize_url($url) {
}
/**
+ * OpenID normalization method: Normalize Google identifiers.
+ *
+ * This transforms a Google identifier (user@domain) into an XRDS URL.
+ *
+ * @see http://sites.google.com/site/oauthgoog/fedlogininterp/openiddiscovery#TOC-IdP-Discovery
+ */
+function _openid_google_idp_normalize($identifier) {
+ if (!valid_email_address($identifier)) {
+ return;
+ }
+
+ // If the identifier is a valid email address, try to discover the domain
+ // with Google Federated Login. We only use the generic URL, because the
+ // domain-specific URL (http://example.com/.well-known/host-meta) cannot
+ // be trusted.
+ list($name, $domain) = explode('@', $identifier, 2);
+ $response = drupal_http_request('https://www.google.com/accounts/o8/.well-known/host-meta?hd=' . rawurlencode($domain));
+ if (isset($response->error) || $response->code != 200) {
+ return;
+ }
+
+ if (preg_match('/Link: <(.*)>/', $response->data, $matches)) {
+ $xrds_url = $matches[1];
+ return $xrds_url;
+ }
+}
+
+/**
* Create a serialized message packet as per spec: $key:$value\n .
*/
function _openid_create_message($data) {
diff --git a/modules/openid/openid.module b/modules/openid/openid.module
index 187cbb05b..87dea680a 100644
--- a/modules/openid/openid.module
+++ b/modules/openid/openid.module
@@ -179,7 +179,7 @@ function openid_login_validate($form, &$form_state) {
function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
module_load_include('inc', 'openid');
- $claimed_id = _openid_normalize($claimed_id);
+ $claimed_id = openid_normalize($claimed_id);
$services = openid_discovery($claimed_id);
$service = _openid_select_service($services);
@@ -263,7 +263,7 @@ function openid_complete($response = array()) {
// to the OpenID Provider, we need to do discovery on the returned
// identififer 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)) {
+ if ($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) {
@@ -298,12 +298,61 @@ function openid_discovery($claimed_id) {
module_load_include('inc', 'openid');
module_load_include('inc', 'openid', 'xrds');
- $services = array();
+ $methods = module_invoke_all('openid_discovery_method_info');
+ drupal_alter('openid_discovery_method_info', $methods);
- $xrds_url = $claimed_id;
+ // Execute each method in turn.
+ foreach ($methods as $method) {
+ $discovered_services = $method($claimed_id);
+ if (!empty($discovered_services)) {
+ return $discovered_services;
+ }
+ }
+
+ return array();
+}
+
+/**
+ * Implementation of hook_openid_discovery_method_info().
+ *
+ * Define standard discovery methods.
+ */
+function openid_openid_discovery_method_info() {
+ // The discovery process will stop as soon as one discovery method succeed.
+ // We first attempt to discover XRI-based identifiers, then standard XRDS
+ // identifiers via Yadis and HTML-based discovery, conforming to the OpenID 2.0
+ // specification. If those fail, we attempt to discover services based on
+ // the Google user discovery scheme.
+ return array(
+ 'xri' => '_openid_xri_discovery',
+ 'xrds' => '_openid_xrds_discovery',
+ 'google' => '_openid_google_user_discovery',
+ );
+}
+
+/**
+ * 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);
}
+}
+
+/**
+ * OpenID discovery method: perform a XRDS discovery.
+ *
+ * @see http://openid.net/specs/openid-authentication-2_0.html#discovery
+ * @see hook_openid_discovery_method_info()
+ */
+function _openid_xrds_discovery($claimed_id) {
+ $services = array();
+
+ $xrds_url = $claimed_id;
$scheme = @parse_url($xrds_url, PHP_URL_SCHEME);
if ($scheme == 'http' || $scheme == 'https') {
// For regular URLs, try Yadis resolution first, then HTML-based discovery
@@ -360,6 +409,57 @@ function openid_discovery($claimed_id) {
}
/**
+ * OpenID discovery method: Perform an user discovery using Google Discovery protocol.
+ *
+ * This transforms a OpenID identifier into an OpenID endpoint.
+ *
+ * @see http://sites.google.com/site/oauthgoog/fedlogininterp/openiddiscovery#TOC-User-Discovery
+ * @see hook_openid_discovery_method_info()
+ */
+function _openid_google_user_discovery($claimed_id) {
+ $xrds_url = $claimed_id;
+ $url = @parse_url($xrds_url);
+ if (empty($url['scheme']) || ($url['scheme'] != 'http' && $scheme['scheme'] != 'https') || empty($url['host'])) {
+ return;
+ }
+
+ $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'];
+ $xrds_url = str_replace('{%uri}', rawurlencode($claimed_id), $template);
+ return _openid_xrds_discovery($xrds_url);
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_openid_normalization_method_info().
+ *
+ * Define standard normalization methods.
+ */
+function openid_openid_normalization_method_info() {
+ // We first try to normalize Google Identifiers (user@domain) into their
+ // corresponding XRDS URL. If this fail, we proceed with standard OpenID
+ // normalization by normalizing XRI idenfiers. Finally, normalize the identifier
+ // into a canonical URL.
+ return array(
+ 'google_idp' => '_openid_google_idp_normalize',
+ 'xri' => '_openid_xri_normalize',
+ 'url' => '_openid_url_normalize',
+ );
+}
+
+/**
* Attempt to create a shared secret with the OpenID Provider.
*
* @param $op_endpoint URL of the OpenID Provider endpoint.
diff --git a/modules/openid/openid.pages.inc b/modules/openid/openid.pages.inc
index 87a4c89a3..ac0d4dc52 100644
--- a/modules/openid/openid.pages.inc
+++ b/modules/openid/openid.pages.inc
@@ -80,7 +80,7 @@ function openid_user_add() {
function openid_user_add_validate($form, &$form_state) {
// Check for existing entries.
- $claimed_id = _openid_normalize($form_state['values']['openid_identifier']);
+ $claimed_id = openid_normalize($form_state['values']['openid_identifier']);
if (db_query("SELECT authname FROM {authmap} WHERE authname = :authname", (array(':authname' => $claimed_id)))->fetchField()) {
form_set_error('openid_identifier', t('That OpenID is already in use on this site.'));
}
diff --git a/modules/openid/openid.test b/modules/openid/openid.test
index 20f920442..fc88158a0 100644
--- a/modules/openid/openid.test
+++ b/modules/openid/openid.test
@@ -445,22 +445,33 @@ class OpenIDUnitTest extends DrupalWebTestCase {
}
/**
- * Test _openid_normalize().
+ * Test openid_normalize().
*/
function testOpenidNormalize() {
// Test that the normalization is according to OpenID Authentication 2.0,
// section 7.2 and 11.5.2.
- $this->assertEqual(_openid_normalize('$foo'), '$foo', t('_openid_normalize() correctly normalized an XRI.'));
- $this->assertEqual(_openid_normalize('xri://$foo'), '$foo', t('_openid_normalize() correctly normalized an XRI with an xri:// scheme.'));
+ $this->assertEqual(openid_normalize('$foo'), '$foo', t('openid_normalize() correctly normalized an XRI.'));
+ $this->assertEqual(openid_normalize('xri://$foo'), '$foo', t('openid_normalize() correctly normalized an XRI with an xri:// scheme.'));
- $this->assertEqual(_openid_normalize('example.com/'), 'http://example.com/', t('_openid_normalize() correctly normalized a URL with a missing scheme.'));
- $this->assertEqual(_openid_normalize('example.com'), 'http://example.com/', t('_openid_normalize() correctly normalized a URL with a missing scheme and empty path.'));
- $this->assertEqual(_openid_normalize('http://example.com'), 'http://example.com/', t('_openid_normalize() correctly normalized a URL with an empty path.'));
+ $this->assertEqual(openid_normalize('example.com/'), 'http://example.com/', t('openid_normalize() correctly normalized a URL with a missing scheme.'));
+ $this->assertEqual(openid_normalize('example.com'), 'http://example.com/', t('openid_normalize() correctly normalized a URL with a missing scheme and empty path.'));
+ $this->assertEqual(openid_normalize('http://example.com'), 'http://example.com/', t('openid_normalize() correctly normalized a URL with an empty path.'));
- $this->assertEqual(_openid_normalize('http://example.com/path'), 'http://example.com/path', t('_openid_normalize() correctly normalized a URL with a path.'));
+ $this->assertEqual(openid_normalize('http://example.com/path'), 'http://example.com/path', t('openid_normalize() correctly normalized a URL with a path.'));
- $this->assertEqual(_openid_normalize('http://example.com/path#fragment'), 'http://example.com/path', t('_openid_normalize() correctly normalized a URL with a fragment.'));
+ $this->assertEqual(openid_normalize('http://example.com/path#fragment'), 'http://example.com/path', t('openid_normalize() correctly normalized a URL with a fragment.'));
}
+ /**
+ * Test _openid_google_idp_normalize().
+ */
+ function testGoogleIdpNormalize() {
+ // We consider that Gmail will always be Gmail.
+ $this->assertTrue(valid_url(_openid_google_idp_normalize('testuser@gmail.com'), TRUE), t('_openid_google_idp_normalize() correctly normalized a Google Gmail identifier.'));
+ // This is a test domain documented on http://sites.google.com/site/oauthgoog/fedlogininterp/saml-idp.
+ $this->assertTrue(valid_url(_openid_google_idp_normalize('test@lso-test-domain.com'), TRUE), t('_openid_google_idp_normalize() correctly normalized a Google Apps for Domain identifier.'));
+ // We consider that microsoft.com will never be hosted by Google.
+ $this->assertFalse(valid_url(_openid_google_idp_normalize('test@microsoft.com'), TRUE), t("_openid_google_idp_normalize() didn't normalized an identifier for a domain that is not Google-enabled."));
+ }
}
diff --git a/modules/openid/xrds.inc b/modules/openid/xrds.inc
index 0c6717ea4..6e05d0c09 100644
--- a/modules/openid/xrds.inc
+++ b/modules/openid/xrds.inc
@@ -71,6 +71,11 @@ function _xrds_cdata(&$parser, $data) {
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;
}
}