diff options
author | Dries Buytaert <dries@buytaert.net> | 2010-03-02 08:59:54 +0000 |
---|---|---|
committer | Dries Buytaert <dries@buytaert.net> | 2010-03-02 08:59:54 +0000 |
commit | 0d9eedc8fb26b550c766b432e3e237c563272ace (patch) | |
tree | 4ca35341dccaa5c83004333dd65cfb652885b69e /modules/openid | |
parent | 094a6b4f85de525d32a0b8787e533e5e77925f7d (diff) | |
download | brdo-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.php | 60 | ||||
-rw-r--r-- | modules/openid/openid.inc | 67 | ||||
-rw-r--r-- | modules/openid/openid.module | 108 | ||||
-rw-r--r-- | modules/openid/openid.pages.inc | 2 | ||||
-rw-r--r-- | modules/openid/openid.test | 27 | ||||
-rw-r--r-- | modules/openid/xrds.inc | 5 |
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; } } |