First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

BIN
modules/openid/login-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

View file

@ -0,0 +1,18 @@
#edit-openid-identifier {
background-position: right 50%;
padding-left: 0;
padding-right: 20px;
}
#user-login .openid-links {
padding-right: 0;
}
html.js #user-login-form li.openid-link,
html.js #user-login li.openid-link {
margin-right: 0;
}
#user-login-form li.openid-link a,
#user-login li.openid-link a {
background-position: right top;
padding: 0 1.5em 0 0;
}

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* Hooks provided by the OpenID module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Allow modules to modify the OpenID request parameters.
*
* @param $op
* The operation to be performed.
* Possible values:
* - request: Modify parameters before they are sent to the OpenID provider.
* @param $request
* An associative array of parameter defaults to which to modify or append.
* @return
* An associative array of parameters to be merged with the default list.
*
*/
function hook_openid($op, $request) {
if ($op == 'request') {
$request['openid.identity'] = 'http://myname.myopenid.com/';
}
return $request;
}
/**
* Allow modules to act upon a successful OpenID login.
*
* @param $response
* Response values from the OpenID Provider.
* @param $account
* The Drupal user account that logged in
*
*/
function hook_openid_response($response, $account) {
if (isset($response['openid.ns.ax'])) {
_mymodule_store_ax_fields($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 associative array
* with array of services and claimed identifier in the same form as returned by
* openid_discover(). The resulting array must contain following keys:
* - 'services' (required) an array of discovered services (including OpenID
* version, endpoint URI, etc).
* - 'claimed_id' (optional) new claimed identifer, found by following HTTP
* redirects during the services discovery.
*
* 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 XRI discovery scheme.
unset($methods['xri']);
}
/**
* 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".
*/

46
modules/openid/openid.css Normal file
View file

@ -0,0 +1,46 @@
#edit-openid-identifier {
background-image: url("login-bg.png");
background-position: left 50%; /* LTR */
background-repeat: no-repeat;
padding-left: 20px; /* LTR */
}
div.form-item-openid-identifier {
display: block;
}
html.js #user-login-form div.form-item-openid-identifier,
html.js #user-login div.form-item-openid-identifier {
display: none;
}
#user-login-form ul {
margin-top: 0;
}
#user-login ul {
margin: 0 0 5px;
}
#user-login ul li {
margin: 0;
}
#user-login-form .openid-links {
padding-bottom: 0;
}
#user-login .openid-links {
padding-left: 0; /* LTR */
}
#user-login-form .openid-links li,
#user-login .openid-links li {
display: none;
list-style: none;
}
html.js #user-login-form li.openid-link,
html.js #user-login li.openid-link {
display: block;
margin-left: 0; /* LTR */
}
#user-login-form li.openid-link a,
#user-login li.openid-link a {
background-image: url("login-bg.png");
background-position: left top; /* LTR */
background-repeat: no-repeat;
padding: 0 0 0 1.5em; /* LTR */
}

899
modules/openid/openid.inc Normal file
View file

@ -0,0 +1,899 @@
<?php
/**
* @file
* OpenID utility functions.
*/
/**
* Diffie-Hellman Key Exchange Default Value.
*
* This is used to establish an association between the Relying Party and the
* OpenID Provider.
*
* See RFC 2631: http://www.ietf.org/rfc/rfc2631.txt
*/
define('OPENID_DH_DEFAULT_MOD', '155172898181473697471232257763715539915724801' .
'966915404479707795314057629378541917580651227423698188993727816152646631' .
'438561595825688188889951272158842675419950341258706556549803580104870537' .
'681476726513255747040765857479291291572334510643245094715007229621094194' .
'349783925984760375594985848253359305585439638443');
/**
* Diffie-Hellman generator; used for Diffie-Hellman key exchange computations.
*/
define('OPENID_DH_DEFAULT_GEN', '2');
/**
* SHA-1 hash block size; used for Diffie-Hellman key exchange computations.
*/
define('OPENID_SHA1_BLOCKSIZE', 64);
/**
* Random number generator; used for Diffie-Hellman key exchange computations.
*/
define('OPENID_RAND_SOURCE', '/dev/urandom');
/**
* OpenID Authentication 2.0 namespace URL.
*/
define('OPENID_NS_2_0', 'http://specs.openid.net/auth/2.0');
/**
* OpenID Authentication 1.1 namespace URL; used for backwards-compatibility.
*/
define('OPENID_NS_1_1', 'http://openid.net/signon/1.1');
/**
* OpenID Authentication 1.0 namespace URL; used for backwards-compatibility.
*/
define('OPENID_NS_1_0', 'http://openid.net/signon/1.0');
/**
* OpenID namespace used in Yadis documents.
*/
define('OPENID_NS_OPENID', 'http://openid.net/xmlns/1.0');
/**
* OpenID Simple Registration extension.
*/
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)');
/**
* 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) ? '?' : '&';
header('Location: ' . $url . $sep . implode('&', $query), TRUE, 302);
drupal_exit();
}
/**
* Creates a js auto-submit redirect for (for the 2.x protocol)
*/
function openid_redirect($url, $message) {
global $language;
$output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
$output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $language->language . '" lang="' . $language->language . '">' . "\n";
$output .= "<head>\n";
$output .= "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
$output .= "<title>" . t('OpenID redirect') . "</title>\n";
$output .= "</head>\n";
$output .= "<body>\n";
$elements = drupal_get_form('openid_redirect_form', $url, $message);
$output .= drupal_render($elements);
$output .= '<script type="text/javascript">document.getElementById("openid-redirect-form").submit();</script>' . "\n";
$output .= "</body>\n";
$output .= "</html>\n";
print $output;
drupal_exit();
}
function openid_redirect_form($form, &$form_state, $url, $message) {
$form['#action'] = $url;
$form['#method'] = "post";
foreach ($message as $key => $value) {
$form[$key] = array(
'#type' => 'hidden',
'#name' => $key,
'#value' => $value,
);
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#prefix' => '<noscript><div>',
'#suffix' => '</div></noscript>',
'#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();
// For PHP version >= 5.2.11, we can use this function to protect against
// malicious doctype declarations and other unexpected entity loading.
// However, we will not rely on it, and reject any XML with a DOCTYPE.
$disable_entity_loader = function_exists('libxml_disable_entity_loader');
if ($disable_entity_loader) {
$load_entities = libxml_disable_entity_loader(TRUE);
}
// Load the XML into a DOM document.
$dom = new DOMDocument();
@$dom->loadXML($raw_xml);
// Since DOCTYPE declarations from an untrusted source could be malicious, we
// stop parsing here and treat the XML as invalid since XRDS documents do not
// require, and are not expected to have, a DOCTYPE.
if (isset($dom->doctype)) {
return array();
}
// Also stop parsing if there is an unreasonably large number of tags.
if ($dom->getElementsByTagName('*')->length > variable_get('openid_xrds_maximum_tag_count', 30000)) {
return array();
}
// Parse the DOM document for the information we need.
if ($xml = simplexml_import_dom($dom)) {
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)->LocalID) {
$service['identity'] = (string)$service_element->children(OPENID_NS_XRD)->LocalID;
}
elseif ($service_element->children(OPENID_NS_OPENID)->Delegate) {
$service['identity'] = (string)$service_element->children(OPENID_NS_OPENID)->Delegate;
}
else {
$service['identity'] = FALSE;
}
$services[] = $service;
}
}
}
// Return the LIBXML options to the previous state before returning.
if ($disable_entity_loader) {
libxml_disable_entity_loader($load_entities);
}
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().
* @return
* The selected service array, or NULL if no valid services were found.
*/
function _openid_select_service(array $services) {
// Extensible Resource Identifier (XRI) Resolution Version 2.0, section 4.3.3:
// Find the service with the highest priority (lowest integer value). If there
// is a tie, select a random one, not just the first in the XML document.
shuffle($services);
$selected_service = NULL;
$selected_type_priority = FALSE;
// Search for an OP Identifier Element.
foreach ($services as $service) {
if (!empty($service['uri'])) {
$type_priority = FALSE;
if (in_array('http://specs.openid.net/auth/2.0/server', $service['types'])) {
$service['version'] = 2;
$type_priority = 1;
}
elseif (in_array('http://specs.openid.net/auth/2.0/signon', $service['types'])) {
$service['version'] = 2;
$type_priority = 2;
}
elseif (in_array(OPENID_NS_1_0, $service['types']) || in_array(OPENID_NS_1_1, $service['types'])) {
$service['version'] = 1;
$type_priority = 3;
}
if ($type_priority
&& (!$selected_service
|| $type_priority < $selected_type_priority
|| ($type_priority == $selected_type_priority && $service['priority'] < $selected_service['priority']))) {
$selected_service = $service;
$selected_type_priority = $type_priority;
}
}
}
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://') === 0) {
$identifier = substr($identifier, 6);
}
// Test whether the identifier starts with an XRI global context symbol or (.
$firstchar = substr($identifier, 0, 1);
if (strpos("=@+$!(", $firstchar) !== FALSE) {
return TRUE;
}
return FALSE;
}
/**
* Normalize the given identifier.
*
* The procedure is described in OpenID Authentication 2.0, section 7.2.
*/
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;
}
/**
* 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;
}
}
/**
* OpenID normalization method: normalize URL identifiers.
*/
function _openid_url_normalize($url) {
$normalized_url = $url;
if (stristr($url, '://') === FALSE) {
$normalized_url = 'http://' . $url;
}
// Strip the fragment and fragment delimiter if present.
$normalized_url = strtok($normalized_url, '#');
if (substr_count($normalized_url, '/') < 3) {
$normalized_url .= '/';
}
return $normalized_url;
}
/**
* Create a serialized message packet as per spec: $key:$value\n .
*/
function _openid_create_message($data) {
$serialized = '';
foreach ($data as $key => $value) {
if ((strpos($key, ':') !== FALSE) || (strpos($key, "\n") !== FALSE) || (strpos($value, "\n") !== FALSE)) {
return NULL;
}
$serialized .= "$key:$value\n";
}
return $serialized;
}
/**
* Encode a message from _openid_create_message for HTTP Post
*/
function _openid_encode_message($message) {
$encoded_message = '';
$items = explode("\n", $message);
foreach ($items as $item) {
$parts = explode(':', $item, 2);
if (count($parts) == 2) {
if ($encoded_message != '') {
$encoded_message .= '&';
}
$encoded_message .= rawurlencode(trim($parts[0])) . '=' . rawurlencode(trim($parts[1]));
}
}
return $encoded_message;
}
/**
* Convert a direct communication message
* into an associative array.
*/
function _openid_parse_message($message) {
$parsed_message = array();
$items = explode("\n", $message);
foreach ($items as $item) {
$parts = explode(':', $item, 2);
if (count($parts) == 2) {
$parsed_message[$parts[0]] = $parts[1];
}
}
return $parsed_message;
}
/**
* Return a nonce value - formatted per OpenID spec.
*
* NOTE: This nonce is not cryptographically secure and only suitable for use
* by the test framework.
*/
function _openid_nonce() {
// YYYY-MM-DDThh:mm:ssZ, plus some optional extra unique characters.
return gmdate('Y-m-d\TH:i:s\Z') .
chr(mt_rand(0, 25) + 65) .
chr(mt_rand(0, 25) + 65) .
chr(mt_rand(0, 25) + 65) .
chr(mt_rand(0, 25) + 65);
}
/**
* Pull the href attribute out of an html link element.
*/
function _openid_link_href($rel, $html) {
$rel = preg_quote($rel);
preg_match('|<link\s+rel=["\'](.*)' . $rel . '(.*)["\'](.*)/?>|iUs', $html, $matches);
if (isset($matches[3])) {
preg_match('|href=["\']([^"]+)["\']|iU', $matches[3], $href);
return trim($href[1]);
}
return FALSE;
}
/**
* Pull the http-equiv attribute out of an html meta element
*/
function _openid_meta_httpequiv($equiv, $html) {
preg_match('|<meta\s+http-equiv=["\']' . $equiv . '["\'](.*)/?>|iUs', $html, $matches);
if (isset($matches[1])) {
preg_match('|content=["\']([^"]+)["\']|iUs', $matches[1], $content);
if (isset($content[1])) {
return $content[1];
}
}
return FALSE;
}
/**
* Sign certain keys in a message
* @param $association - object loaded from openid_association or openid_server_association table
* - important fields are ->assoc_type and ->mac_key
* @param $message_array - array of entire message about to be sent
* @param $keys_to_sign - keys in the message to include in signature (without
* 'openid.' appended)
*/
function _openid_signature($association, $message_array, $keys_to_sign) {
$signature = '';
$sign_data = array();
foreach ($keys_to_sign as $key) {
if (isset($message_array['openid.' . $key])) {
$sign_data[$key] = $message_array['openid.' . $key];
}
}
$message = _openid_create_message($sign_data);
$secret = base64_decode($association->mac_key);
$signature = _openid_hmac($secret, $message);
return base64_encode($signature);
}
function _openid_hmac($key, $text) {
if (strlen($key) > OPENID_SHA1_BLOCKSIZE) {
$key = sha1($key, TRUE);
}
$key = str_pad($key, OPENID_SHA1_BLOCKSIZE, chr(0x00));
$ipad = str_repeat(chr(0x36), OPENID_SHA1_BLOCKSIZE);
$opad = str_repeat(chr(0x5c), OPENID_SHA1_BLOCKSIZE);
$hash1 = sha1(($key ^ $ipad) . $text, TRUE);
$hmac = sha1(($key ^ $opad) . $hash1, TRUE);
return $hmac;
}
function _openid_dh_base64_to_long($str) {
$b64 = base64_decode($str);
return _openid_dh_binary_to_long($b64);
}
function _openid_dh_long_to_base64($str) {
return base64_encode(_openid_dh_long_to_binary($str));
}
function _openid_dh_binary_to_long($str) {
$bytes = array_merge(unpack('C*', $str));
$n = 0;
foreach ($bytes as $byte) {
$n = _openid_math_mul($n, pow(2, 8));
$n = _openid_math_add($n, $byte);
}
return $n;
}
function _openid_dh_long_to_binary($long) {
$cmp = _openid_math_cmp($long, 0);
if ($cmp < 0) {
return FALSE;
}
if ($cmp == 0) {
return "\x00";
}
$bytes = array();
while (_openid_math_cmp($long, 0) > 0) {
array_unshift($bytes, _openid_math_mod($long, 256));
$long = _openid_math_div($long, pow(2, 8));
}
if ($bytes && ($bytes[0] > 127)) {
array_unshift($bytes, 0);
}
$string = '';
foreach ($bytes as $byte) {
$string .= pack('C', $byte);
}
return $string;
}
function _openid_dh_xorsecret($shared, $secret) {
$dh_shared_str = _openid_dh_long_to_binary($shared);
$sha1_dh_shared = sha1($dh_shared_str, TRUE);
$xsecret = "";
for ($i = 0; $i < strlen($secret); $i++) {
$xsecret .= chr(ord($secret[$i]) ^ ord($sha1_dh_shared[$i]));
}
return $xsecret;
}
function _openid_dh_rand($stop) {
$duplicate_cache = &drupal_static(__FUNCTION__, array());
// Used as the key for the duplicate cache
$rbytes = _openid_dh_long_to_binary($stop);
if (isset($duplicate_cache[$rbytes])) {
list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
}
else {
if ($rbytes[0] == "\x00") {
$nbytes = strlen($rbytes) - 1;
}
else {
$nbytes = strlen($rbytes);
}
$mxrand = _openid_math_pow(256, $nbytes);
// If we get a number less than this, then it is in the
// duplicated range.
$duplicate = _openid_math_mod($mxrand, $stop);
if (count($duplicate_cache) > 10) {
$duplicate_cache = array();
}
$duplicate_cache[$rbytes] = array($duplicate, $nbytes);
}
do {
$bytes = "\x00" . drupal_random_bytes($nbytes);
$n = _openid_dh_binary_to_long($bytes);
// Keep looping if this value is in the low duplicated range.
} while (_openid_math_cmp($n, $duplicate) < 0);
return _openid_math_mod($n, $stop);
}
function _openid_get_bytes($num_bytes) {
return drupal_random_bytes($num_bytes);
}
function _openid_response($str = NULL) {
$data = array();
if (isset($_SERVER['REQUEST_METHOD'])) {
$data = _openid_get_params($_SERVER['QUERY_STRING']);
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$str = file_get_contents('php://input');
$post = array();
if ($str !== FALSE) {
$post = _openid_get_params($str);
}
$data = array_merge($data, $post);
}
}
return $data;
}
function _openid_get_params($str) {
$chunks = explode("&", $str);
$data = array();
foreach ($chunks as $chunk) {
$parts = explode("=", $chunk, 2);
if (count($parts) == 2) {
list($k, $v) = $parts;
$data[$k] = urldecode($v);
}
}
return $data;
}
/**
* Extract all the parameters belonging to an extension in a response message.
*
* OpenID 2.0 defines a simple extension mechanism, based on a namespace prefix.
*
* Each request or response can define a prefix using:
* @code
* openid.ns.[prefix] = [extension_namespace]
* openid.[prefix].[key1] = [value1]
* openid.[prefix].[key2] = [value2]
* ...
* @endcode
*
* This function extracts all the keys belonging to an extension namespace in a
* response, optionally using a fallback prefix if none is provided in the response.
*
* Note that you cannot assume that a given extension namespace will use the same
* prefix on the response and the request: each party may use a different prefix
* to refer to the same namespace.
*
* @param $response
* The response array.
* @param $extension_namespace
* The namespace of the extension.
* @param $fallback_prefix
* An optional prefix that will be used in case no prefix is found for the
* target extension namespace.
* @param $only_signed
* Return only keys that are included in the message signature in openid.sig.
* Unsigned fields may have been modified or added by other parties than the
* OpenID Provider.
*
* @return
* An associative array containing all the parameters in the response message
* that belong to the extension. The keys are stripped from their namespace
* prefix.
*
* @see http://openid.net/specs/openid-authentication-2_0.html#extensions
*/
function openid_extract_namespace($response, $extension_namespace, $fallback_prefix = NULL, $only_signed = FALSE) {
$signed_keys = explode(',', $response['openid.signed']);
// Find the namespace prefix.
$prefix = $fallback_prefix;
foreach ($response as $key => $value) {
if ($value == $extension_namespace && preg_match('/^openid\.ns\.([^.]+)$/', $key, $matches)) {
$prefix = $matches[1];
if ($only_signed && !in_array('ns.' . $matches[1], $signed_keys)) {
// The namespace was defined but was not signed as required. In this
// case we do not fall back to $fallback_prefix.
$prefix = NULL;
}
break;
}
}
// Now extract the namespace keys from the response.
$output = array();
if (!isset($prefix)) {
return $output;
}
foreach ($response as $key => $value) {
if (preg_match('/^openid\.' . $prefix . '\.(.+)$/', $key, $matches)) {
$local_key = $matches[1];
if (!$only_signed || in_array($prefix . '.' . $local_key, $signed_keys)) {
$output[$local_key] = $value;
}
}
}
return $output;
}
/**
* Extracts values from an OpenID AX Response.
*
* The values can be returned in two forms:
* - only openid.ax.value.<alias> (for single-valued answers)
* - both openid.ax.count.<alias> and openid.ax.value.<alias>.<count> (for both
* single and multiple-valued answers)
*
* @param $values
* An array as returned by openid_extract_namespace(..., OPENID_NS_AX).
* @param $uris
* An array of identifier URIs.
* @return
* An array of values.
* @see http://openid.net/specs/openid-attribute-exchange-1_0.html#fetch_response
*/
function openid_extract_ax_values($values, $uris) {
$output = array();
foreach ($values as $key => $value) {
if (in_array($value, $uris) && preg_match('/^type\.([^.]+)$/', $key, $matches)) {
$alias = $matches[1];
if (isset($values['count.' . $alias])) {
for ($i = 1; $i <= $values['count.' . $alias]; $i++) {
$output[] = $values['value.' . $alias . '.' . $i];
}
}
elseif (isset($values['value.' . $alias])) {
$output[] = $values['value.' . $alias];
}
break;
}
}
return $output;
}
/**
* Determine the available math library GMP vs. BCMath, favouring GMP for performance.
*/
function _openid_get_math_library() {
// Not drupal_static(), because a function is not going to disappear and
// change the output of this under any circumstances.
static $library;
if (empty($library)) {
if (function_exists('gmp_add')) {
$library = 'gmp';
}
elseif (function_exists('bcadd')) {
$library = 'bcmath';
}
}
return $library;
}
/**
* Calls the add function from the available math library for OpenID.
*/
function _openid_math_add($x, $y) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_strval(gmp_add($x, $y));
case 'bcmath':
return bcadd($x, $y);
}
}
/**
* Calls the mul function from the available math library for OpenID.
*/
function _openid_math_mul($x, $y) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_mul($x, $y);
case 'bcmath':
return bcmul($x, $y);
}
}
/**
* Calls the div function from the available math library for OpenID.
*/
function _openid_math_div($x, $y) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_div($x, $y);
case 'bcmath':
return bcdiv($x, $y);
}
}
/**
* Calls the cmp function from the available math library for OpenID.
*/
function _openid_math_cmp($x, $y) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_cmp($x, $y);
case 'bcmath':
return bccomp($x, $y);
}
}
/**
* Calls the mod function from the available math library for OpenID.
*/
function _openid_math_mod($x, $y) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_mod($x, $y);
case 'bcmath':
return bcmod($x, $y);
}
}
/**
* Calls the pow function from the available math library for OpenID.
*/
function _openid_math_pow($x, $y) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_pow($x, $y);
case 'bcmath':
return bcpow($x, $y);
}
}
/**
* Calls the mul function from the available math library for OpenID.
*/
function _openid_math_powmod($x, $y, $z) {
$library = _openid_get_math_library();
switch ($library) {
case 'gmp':
return gmp_powm($x, $y, $z);
case 'bcmath':
return bcpowmod($x, $y, $z);
}
}
/**
* Provides transition for accounts with possibly invalid OpenID identifiers in authmap.
*
* This function provides a less safe but more unobtrusive procedure for users
* who cannot login with their OpenID identifiers. OpenID identifiers in the
* authmap could be incomplete due to invalid OpenID implementation in previous
* versions of Drupal (e.g. fragment part of the identifier could be missing).
* For more information see http://drupal.org/node/1120290.
*
* @param string $identity
* The user's claimed OpenID identifier.
*
* @return
* A fully-loaded user object if the user is found or FALSE if not found.
*/
function _openid_invalid_openid_transition($identity, $response) {
$account = FALSE;
$fallback_account = NULL;
$fallback_identity = $identity;
// Try to strip the fragment if it is present.
if (strpos($fallback_identity, '#') !== FALSE) {
$fallback_identity = preg_replace('/#.*/', '', $fallback_identity);
$fallback_account = user_external_load($fallback_identity);
}
// Try to replace HTTPS with HTTP. OpenID providers often redirect
// from http to https, but Drupal didn't follow the redirect.
if (!$fallback_account && strpos($fallback_identity, 'https://') !== FALSE) {
$fallback_identity = str_replace('https://', 'http://', $fallback_identity);
$fallback_account = user_external_load($fallback_identity);
}
// Try to use original identifier.
if (!$fallback_account && isset($_SESSION['openid']['user_login_values']['openid_identifier'])) {
$fallback_identity = openid_normalize($_SESSION['openid']['user_login_values']['openid_identifier']);
$fallback_account = user_external_load($fallback_identity);
}
if ($fallback_account) {
// Try to extract e-mail address from Simple Registration (SREG) or
// Attribute Exchanges (AX) keys.
$email = '';
$sreg_values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', TRUE);
$ax_values = openid_extract_namespace($response, OPENID_NS_AX, 'ax', TRUE);
if (!empty($sreg_values['email']) && valid_email_address($sreg_values['email'])) {
$email = $sreg_values['email'];
}
elseif ($ax_mail_values = openid_extract_ax_values($ax_values, array('http://axschema.org/contact/email', 'http://schema.openid.net/contact/email'))) {
$email = current($ax_mail_values);
}
// If this e-mail address is the same as the e-mail address found in user
// account, login the user and update the claimed identifier.
if ($email && ($email == $fallback_account->mail || $email == $fallback_account->init)) {
$query = db_insert('authmap')
->fields(array(
'authname' => $identity,
'uid' => $fallback_account->uid,
'module' => 'openid',
))
->execute();
drupal_set_message(t('New OpenID identifier %identity was added as a replacement for invalid identifier %invalid_identity. To finish the invalid OpenID transition process, please go to your <a href="@openid_url">OpenID identities page</a> and remove the old identifier %invalid_identity.', array('%invalid_identity' => $fallback_identity, '%identity' => $identity, '@openid_url' => 'user/' . $fallback_account->uid . '/openid')));
// Set the account to the found one.
$account = $fallback_account;
}
else {
drupal_set_message(t('There is already an existing account associated with the OpenID identifier that you have provided. However, due to a bug in the previous version of the authentication system, we can\'t be sure that this account belongs to you. If you are new on this site, please continue registering the new user account. If you already have a registered account on this site associated with the provided OpenID identifier, please try to <a href="@url_password">reset the password</a> or contact the site administrator.', array('@url_password' => 'user/password')), 'warning');
}
}
return $account;
}

View file

@ -0,0 +1,12 @@
name = OpenID
description = "Allows users to log into your site using OpenID."
version = VERSION
package = Core
core = 7.x
files[] = openid.test
; Information added by Drupal.org packaging script on 2017-06-21
version = "7.56"
project = "drupal"
datestamp = "1498069849"

View file

@ -0,0 +1,230 @@
<?php
/**
* @file
* Install, update and uninstall functions for the openid module.
*/
/**
* Implements hook_schema().
*/
function openid_schema() {
$schema['openid_association'] = array(
'description' => 'Stores temporary shared key association information for OpenID authentication.',
'fields' => array(
'idp_endpoint_uri' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Primary Key: URI of the OpenID Provider endpoint.',
),
'assoc_handle' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Used to refer to this association in subsequent messages.',
),
'assoc_type' => array(
'type' => 'varchar',
'length' => 32,
'description' => 'The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.',
),
'session_type' => array(
'type' => 'varchar',
'length' => 32,
'description' => 'Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".',
),
'mac_key' => array(
'type' => 'varchar',
'length' => 255,
'description' => 'The MAC key (shared secret) for this association.',
),
'created' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'UNIX timestamp for when the association was created.',
),
'expires_in' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'The lifetime, in seconds, of this association.',
),
),
'primary key' => array('idp_endpoint_uri'),
'unique keys' => array(
'assoc_handle' => array('assoc_handle'),
),
);
$schema['openid_nonce'] = array(
'description' => 'Stores received openid.response_nonce per OpenID endpoint URL to prevent replay attacks.',
'fields' => array(
'idp_endpoint_uri' => array(
'type' => 'varchar',
'length' => 255,
'description' => 'URI of the OpenID Provider endpoint.',
),
'nonce' => array(
'type' => 'varchar',
'length' => 255,
'description' => 'The value of openid.response_nonce.',
),
'expires' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'A Unix timestamp indicating when the entry should expire.',
),
),
'indexes' => array(
'nonce' => array('nonce'),
'expires' => array('expires'),
),
);
return $schema;
}
/**
* Implements hook_requirements().
*/
function openid_requirements($phase) {
$requirements = array();
if ($phase == 'runtime') {
// Check for the PHP BC Math library.
if (!function_exists('bcadd') && !function_exists('gmp_add')) {
$requirements['openid_math'] = array(
'value' => t('Not installed'),
'severity' => REQUIREMENT_ERROR,
'description' => t('OpenID suggests the use of either the <a href="@gmp">GMP Math</a> (recommended for performance) or <a href="@bc">BC Math</a> libraries to enable OpenID associations.', array('@gmp' => 'http://php.net/manual/en/book.gmp.php', '@bc' => 'http://www.php.net/manual/en/book.bc.php')),
);
}
elseif (!function_exists('gmp_add')) {
$requirements['openid_math'] = array(
'value' => t('Not optimized'),
'severity' => REQUIREMENT_WARNING,
'description' => t('OpenID suggests the use of the GMP Math library for PHP for optimal performance. Check the <a href="@url">GMP Math Library documentation</a> for installation instructions.', array('@url' => 'http://www.php.net/manual/en/book.gmp.php')),
);
}
else {
$requirements['openid_math'] = array(
'value' => t('Installed'),
'severity' => REQUIREMENT_OK,
);
}
$requirements['openid_math']['title'] = t('OpenID Math library');
}
return $requirements;
}
/**
* @addtogroup updates-6.x-to-7.x
* @{
*/
/**
* Add a table to store nonces.
*/
function openid_update_6000() {
$schema['openid_nonce'] = array(
'description' => 'Stores received openid.response_nonce per OpenID endpoint URL to prevent replay attacks.',
'fields' => array(
'idp_endpoint_uri' => array(
'type' => 'varchar',
'length' => 255,
'description' => 'URI of the OpenID Provider endpoint.',
),
'nonce' => array(
'type' => 'varchar',
'length' => 255,
'description' => 'The value of openid.response_nonce'
),
'expires' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'A Unix timestamp indicating when the entry should expire.',
),
),
'indexes' => array(
'nonce' => array('nonce'),
'expires' => array('expires'),
),
);
db_create_table('openid_nonce', $schema['openid_nonce']);
}
/**
* @} End of "addtogroup updates-6.x-to-7.x".
*/
/**
* @addtogroup updates-7.x-extra
* @{
*/
/**
* Bind associations to their providers.
*/
function openid_update_7000() {
db_drop_table('openid_association');
$schema = array(
'description' => 'Stores temporary shared key association information for OpenID authentication.',
'fields' => array(
'idp_endpoint_uri' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Primary Key: URI of the OpenID Provider endpoint.',
),
'assoc_handle' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'description' => 'Used to refer to this association in subsequent messages.',
),
'assoc_type' => array(
'type' => 'varchar',
'length' => 32,
'description' => 'The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.',
),
'session_type' => array(
'type' => 'varchar',
'length' => 32,
'description' => 'Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".',
),
'mac_key' => array(
'type' => 'varchar',
'length' => 255,
'description' => 'The MAC key (shared secret) for this association.',
),
'created' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'UNIX timestamp for when the association was created.',
),
'expires_in' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'The lifetime, in seconds, of this association.',
),
),
'primary key' => array('idp_endpoint_uri'),
'unique keys' => array(
'assoc_handle' => array('assoc_handle'),
),
);
db_create_table('openid_association', $schema);
}
/**
* @} End of "addtogroup updates-7.x-extra".
*/

49
modules/openid/openid.js Normal file
View file

@ -0,0 +1,49 @@
(function ($) {
Drupal.behaviors.openid = {
attach: function (context) {
var loginElements = $('.form-item-name, .form-item-pass, li.openid-link');
var openidElements = $('.form-item-openid-identifier, li.user-link');
var cookie = $.cookie('Drupal.visitor.openid_identifier');
// This behavior attaches by ID, so is only valid once on a page.
if (!$('#edit-openid-identifier.openid-processed').length) {
if (cookie) {
$('#edit-openid-identifier').val(cookie);
}
if ($('#edit-openid-identifier').val() || location.hash == '#openid-login') {
$('#edit-openid-identifier').addClass('openid-processed');
loginElements.hide();
// Use .css('display', 'block') instead of .show() to be Konqueror friendly.
openidElements.css('display', 'block');
}
}
$('li.openid-link:not(.openid-processed)', context)
.addClass('openid-processed')
.click(function () {
loginElements.hide();
openidElements.css('display', 'block');
// Remove possible error message.
$('#edit-name, #edit-pass').removeClass('error');
$('div.messages.error').hide();
// Set focus on OpenID Identifier field.
$('#edit-openid-identifier')[0].focus();
return false;
});
$('li.user-link:not(.openid-processed)', context)
.addClass('openid-processed')
.click(function () {
openidElements.hide();
loginElements.css('display', 'block');
// Clear OpenID Identifier field and remove possible error message.
$('#edit-openid-identifier').val('').removeClass('error');
$('div.messages.error').css('display', 'block');
// Set focus on username field.
$('#edit-name')[0].focus();
return false;
});
}
};
})(jQuery);

1040
modules/openid/openid.module Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* User page callbacks for the openid module.
*/
/**
* Menu callback; Process an OpenID authentication.
*/
function openid_authentication_page() {
$result = openid_complete();
switch ($result['status']) {
case 'success':
return openid_authentication($result);
case 'failed':
drupal_set_message(t('OpenID login failed.'), 'error');
break;
case 'cancel':
drupal_set_message(t('OpenID login cancelled.'));
break;
}
drupal_goto();
}
/**
* Menu callback; Manage OpenID identities for the specified user.
*/
function openid_user_identities($account) {
drupal_set_title(format_username($account));
drupal_add_css(drupal_get_path('module', 'openid') . '/openid.css');
// Check to see if we got a response
$result = openid_complete();
if ($result['status'] == 'success') {
$identity = $result['openid.claimed_id'];
$query = db_insert('authmap')
->fields(array(
'uid' => $account->uid,
'authname' => $identity,
'module' => 'openid',
))
->execute();
drupal_set_message(t('Successfully added %identity', array('%identity' => $identity)));
}
$header = array(t('OpenID'), t('Operations'));
$rows = array();
$result = db_query("SELECT * FROM {authmap} WHERE module='openid' AND uid=:uid", array(':uid' => $account->uid));
foreach ($result as $identity) {
$rows[] = array(check_plain($identity->authname), l(t('Delete'), 'user/' . $account->uid . '/openid/delete/' . $identity->aid));
}
$build['openid_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('No OpenID identities available for this account.'),
);
$build['openid_user_add'] = drupal_get_form('openid_user_add');
return $build;
}
/**
* Form builder; Add an OpenID identity.
*
* @ingroup forms
* @see openid_user_add_validate()
*/
function openid_user_add() {
$form['openid_identifier'] = array(
'#type' => 'textfield',
'#title' => t('OpenID'),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Add an OpenID'));
return $form;
}
function openid_user_add_validate($form, &$form_state) {
// Check for existing entries.
$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.'));
}
}
function openid_user_add_submit($form, &$form_state) {
$return_to = url('user/' . arg(1) . '/openid', array('absolute' => TRUE));
openid_begin($form_state['values']['openid_identifier'], $return_to);
}
/**
* Menu callback; Delete the specified OpenID identity from the system.
*/
function openid_user_delete_form($form, $form_state, $account, $aid = 0) {
$authname = db_query("SELECT authname FROM {authmap} WHERE uid = :uid AND aid = :aid AND module = 'openid'", array(
':uid' => $account->uid,
':aid' => $aid,
))
->fetchField();
return confirm_form(array(), t('Are you sure you want to delete the OpenID %authname for %user?', array('%authname' => $authname, '%user' => $account->name)), 'user/' . $account->uid . '/openid');
}
function openid_user_delete_form_submit($form, &$form_state) {
$query = db_delete('authmap')
->condition('uid', $form_state['build_info']['args'][0]->uid)
->condition('aid', $form_state['build_info']['args'][1])
->condition('module', 'openid')
->execute();
if ($query) {
drupal_set_message(t('OpenID deleted.'));
}
$form_state['redirect'] = 'user/' . $form_state['build_info']['args'][0]->uid . '/openid';
}

802
modules/openid/openid.test Normal file
View file

@ -0,0 +1,802 @@
<?php
/**
* @file
* Tests for openid.module.
*/
/**
* Base class for OpenID tests.
*/
abstract class OpenIDWebTestCase extends DrupalWebTestCase {
/**
* Initiates the login procedure using the specified User-supplied Identity.
*/
function submitLoginForm($identity) {
// Fill out and submit the login form.
$edit = array('openid_identifier' => $identity);
$this->drupalPost('', $edit, t('Log in'));
// Check we are on the OpenID redirect form.
$this->assertTitle(t('OpenID redirect'), 'OpenID redirect page was displayed.');
// Submit form to the OpenID Provider Endpoint.
$this->drupalPost(NULL, array(), t('Send'));
}
/**
* Parses the last sent e-mail and returns the one-time login link URL.
*/
function getPasswordResetURLFromMail() {
$mails = $this->drupalGetMails();
$mail = end($mails);
preg_match('@.+user/reset/.+@', $mail['body'], $matches);
return $matches[0];
}
}
/**
* Test discovery and login using OpenID
*/
class OpenIDFunctionalTestCase extends OpenIDWebTestCase {
protected $web_user;
public static function getInfo() {
return array(
'name' => 'OpenID discovery and login',
'description' => "Adds an identity to a user's profile and uses it to log in.",
'group' => 'OpenID'
);
}
function setUp() {
parent::setUp('openid', 'openid_test');
// User doesn't need special permissions; only the ability to log in.
$this->web_user = $this->drupalCreateUser(array());
}
/**
* Test discovery of OpenID Provider Endpoint via Yadis and HTML.
*/
function testDiscovery() {
$this->drupalLogin($this->web_user);
// The User-supplied Identifier entered by the user may indicate the URL of
// the OpenID Provider Endpoint in various ways, as described in OpenID
// Authentication 2.0 and Yadis Specification 1.0.
// Note that all of the tested identifiers refer to the same endpoint, so
// only the first will trigger an associate request in openid_association()
// (association is only done the first time Drupal encounters a given
// endpoint).
// Yadis discovery (see Yadis Specification 1.0, section 6.2.5):
// If the User-supplied Identifier is a URL, it may be a direct or indirect
// reference to an XRDS document (a Yadis Resource Descriptor) that contains
// the URL of the OpenID Provider Endpoint.
// Identifier is the URL of an XRDS document.
// On HTTP test environments, the URL scheme is stripped in order to test
// that the supplied identifier is normalized in openid_begin().
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->addIdentity(preg_replace('@^http://@', '', $identity), 2, 'http://example.com/xrds', $identity);
$identity = url('openid-test/yadis/xrds/delegate', array('absolute' => TRUE));
$this->addIdentity(preg_replace('@^http://@', '', $identity), 2, 'http://example.com/xrds-delegate', $identity);
// Identifier is the URL of an XRDS document containing an OP Identifier
// Element. The Relying Party sends the special value
// "http://specs.openid.net/auth/2.0/identifier_select" as Claimed
// Identifier. The OpenID Provider responds with the actual identifier
// including the fragment.
$identity = url('openid-test/yadis/xrds/dummy-user', array('absolute' => TRUE, 'fragment' => $this->randomName()));
// Tell openid_test.module to respond with this identifier. If the fragment
// part is present in the identifier, it should be retained.
variable_set('openid_test_response', array('openid.claimed_id' => $identity, 'openid.identity' => openid_normalize($identity)));
$this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity);
variable_set('openid_test_response', array());
// 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 <meta http-equiv=...>
// 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/xrds', 'http://example.com/user');
// Make sure that unverified CanonicalID are not trusted.
variable_set('openid_test_canonical_id_status', 'bad value');
$this->addIdentity('@example*résumé;%25', 2, FALSE, FALSE);
// HTML-based discovery:
// If the User-supplied Identifier is a URL of an HTML page, the page may
// contain a <link rel=...> 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, 'http://example.com/html-openid1');
// OpenID Authentication 2.0, section 7.3.3:
$this->addIdentity(url('openid-test/html/openid2', array('absolute' => TRUE)), 2, 'http://example.com/html-openid2');
// OpenID Authentication 2.0, section 7.2.4:
// URL Identifiers MUST then be further normalized by both (1) following
// redirects when retrieving their content and finally (2) applying the
// rules in Section 6 of RFC3986 to the final destination URL. This final
// URL MUST be noted by the Relying Party as the Claimed Identifier and be
// used when requesting authentication.
// Single redirect.
$identity = $expected_claimed_id = url('openid-test/redirected/yadis/xrds/1', array('absolute' => TRUE));
$this->addRedirectedIdentity($identity, 2, 'http://example.com/xrds', $expected_claimed_id, 0);
// Exact 3 redirects (default value for the 'max_redirects' option in
// drupal_http_request()).
$identity = $expected_claimed_id = url('openid-test/redirected/yadis/xrds/2', array('absolute' => TRUE));
$this->addRedirectedIdentity($identity, 2, 'http://example.com/xrds', $expected_claimed_id, 2);
// Fails because there are more than 3 redirects (default value for the
// 'max_redirects' option in drupal_http_request()).
$identity = url('openid-test/redirected/yadis/xrds/3', array('absolute' => TRUE));
$expected_claimed_id = FALSE;
$this->addRedirectedIdentity($identity, 2, 'http://example.com/xrds', $expected_claimed_id, 3);
}
/**
* Test login using OpenID.
*/
function testLogin() {
$this->drupalLogin($this->web_user);
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->addIdentity($identity);
$this->drupalLogout();
// Test logging in via the login block on the front page.
$this->submitLoginForm($identity);
$this->assertLink(t('Log out'), 0, 'User was logged in.');
$this->drupalLogout();
// Test logging in via the user/login page.
$edit = array('openid_identifier' => $identity);
$this->drupalPost('user/login', $edit, t('Log in'));
// Check we are on the OpenID redirect form.
$this->assertTitle(t('OpenID redirect'), 'OpenID redirect page was displayed.');
// Submit form to the OpenID Provider Endpoint.
$this->drupalPost(NULL, array(), t('Send'));
$this->assertLink(t('Log out'), 0, 'User was logged in.');
// Verify user was redirected away from user/login to an accessible page.
$this->assertResponse(200);
$this->drupalLogout();
// Use a User-supplied Identity that is the URL of an XRDS document.
// Tell the test module to add a doctype. This should fail.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE, 'query' => array('doctype' => 1)));
// Test logging in via the login block on the front page.
$edit = array('openid_identifier' => $identity);
$this->drupalPost('', $edit, t('Log in'));
$this->assertRaw(t('Sorry, that is not a valid OpenID. Ensure you have spelled your ID correctly.'), 'XML with DOCTYPE was rejected.');
}
/**
* Test login using OpenID during maintenance mode.
*/
function testLoginMaintenanceMode() {
$this->web_user = $this->drupalCreateUser(array('access site in maintenance mode'));
$this->drupalLogin($this->web_user);
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->addIdentity($identity);
$this->drupalLogout();
// Enable maintenance mode.
variable_set('maintenance_mode', 1);
// Test logging in via the user/login page while the site is offline.
$edit = array('openid_identifier' => $identity);
$this->drupalPost('user/login', $edit, t('Log in'));
// Check we are on the OpenID redirect form.
$this->assertTitle(t('OpenID redirect'), 'OpenID redirect page was displayed.');
// Submit form to the OpenID Provider Endpoint.
$this->drupalPost(NULL, array(), t('Send'));
$this->assertLink(t('Log out'), 0, 'User was logged in.');
// Verify user was redirected away from user/login to an accessible page.
$this->assertText(t('Operating in maintenance mode.'));
$this->assertResponse(200);
}
/**
* Test deleting an OpenID identity from a user's profile.
*/
function testDelete() {
$this->drupalLogin($this->web_user);
// Add identity to user's profile.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->addIdentity($identity);
$this->assertText($identity, 'Identity appears in list.');
// Delete the newly added identity.
$this->clickLink(t('Delete'));
$this->drupalPost(NULL, array(), t('Confirm'));
$this->assertText(t('OpenID deleted.'), 'Identity deleted');
$this->assertNoText($identity, 'Identity no longer appears in list.');
}
/**
* Test that a blocked user cannot log in.
*/
function testBlockedUserLogin() {
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
// Log in and add an OpenID Identity to the account.
$this->drupalLogin($this->web_user);
$this->addIdentity($identity);
$this->drupalLogout();
// Log in as an admin user and block the account.
$admin_user = $this->drupalCreateUser(array('administer users'));
$this->drupalLogin($admin_user);
$this->drupalGet('admin/people');
$edit = array(
'operation' => 'block',
'accounts[' . $this->web_user->uid . ']' => TRUE,
);
$this->drupalPost('admin/people', $edit, t('Update'));
$this->assertRaw('The update has been performed.', 'Account was blocked.');
$this->drupalLogout();
$this->submitLoginForm($identity);
$this->assertRaw(t('The username %name has not been activated or is blocked.', array('%name' => $this->web_user->name)), 'User login was blocked.');
}
/**
* Add OpenID identity to user's profile.
*
* @param $identity
* The User-supplied Identifier.
* @param $version
* The protocol version used by the service.
* @param $local_id
* The expected OP-Local Identifier found during discovery.
* @param $claimed_id
* The expected Claimed Identifier returned by the OpenID Provider, or FALSE
* if the discovery is expected to fail.
*/
function addIdentity($identity, $version = 2, $local_id = 'http://example.com/xrds', $claimed_id = NULL) {
// Tell openid_test.module to only accept this OP-Local Identifier.
variable_set('openid_test_identity', $local_id);
$edit = array('openid_identifier' => $identity);
$this->drupalPost('user/' . $this->web_user->uid . '/openid', $edit, t('Add an OpenID'));
if ($claimed_id === FALSE) {
$this->assertRaw(t('Sorry, that is not a valid OpenID. Ensure you have spelled your ID correctly.'), 'Invalid identity was rejected.');
return;
}
// OpenID 1 used a HTTP redirect, OpenID 2 uses a HTML form that is submitted automatically using JavaScript.
if ($version == 2) {
// Check we are on the OpenID redirect form.
$this->assertTitle(t('OpenID redirect'), 'OpenID redirect page was displayed.');
// Submit form to the OpenID Provider Endpoint.
$this->drupalPost(NULL, array(), t('Send'));
}
if (!isset($claimed_id)) {
$claimed_id = $identity;
}
$this->assertRaw(t('Successfully added %identity', array('%identity' => $claimed_id)), format_string('Identity %identity was added.', array('%identity' => $identity)));
}
/**
* Add OpenID identity, changed by the following redirects, to user's profile.
*
* According to OpenID Authentication 2.0, section 7.2.4, URL Identifiers MUST
* be further normalized by following redirects when retrieving their content
* and this final URL MUST be noted by the Relying Party as the Claimed
* Identifier and be used when requesting authentication.
*
* @param $identity
* The User-supplied Identifier.
* @param $version
* The protocol version used by the service.
* @param $local_id
* The expected OP-Local Identifier found during discovery.
* @param $claimed_id
* The expected Claimed Identifier returned by the OpenID Provider, or FALSE
* if the discovery is expected to fail.
* @param $redirects
* The number of redirects.
*/
function addRedirectedIdentity($identity, $version = 2, $local_id = 'http://example.com/xrds', $claimed_id = NULL, $redirects = 0) {
// Set the final destination URL which is the same as the Claimed
// Identifier, we insert the same identifier also to the provider response,
// but provider could further change the Claimed ID actually (e.g. it could
// add unique fragment).
variable_set('openid_test_redirect_url', $identity);
variable_set('openid_test_response', array('openid.claimed_id' => $identity));
$this->addIdentity(url('openid-test/redirect/' . $redirects, array('absolute' => TRUE)), $version, $local_id, $claimed_id);
// Clean up.
variable_del('openid_test_redirect_url');
variable_del('openid_test_response');
}
/**
* Tests that openid.signed is verified.
*/
function testSignatureValidation() {
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
// Respond with an invalid signature.
variable_set('openid_test_response', array('openid.sig' => 'this-is-an-invalid-signature'));
$this->submitLoginForm($identity);
$this->assertRaw('OpenID login failed.');
// Do not sign the mandatory field openid.assoc_handle.
variable_set('openid_test_response', array('openid.signed' => 'op_endpoint,claimed_id,identity,return_to,response_nonce'));
$this->submitLoginForm($identity);
$this->assertRaw('OpenID login failed.');
// Sign all mandatory fields and a custom field.
$keys_to_sign = array('op_endpoint', 'claimed_id', 'identity', 'return_to', 'response_nonce', 'assoc_handle', 'foo');
$association = new stdClass();
$association->mac_key = variable_get('mac_key');
$response = array(
'openid.op_endpoint' => url('openid-test/endpoint', array('absolute' => TRUE)),
'openid.claimed_id' => $identity,
'openid.identity' => $identity,
'openid.return_to' => url('openid/authenticate', array('absolute' => TRUE)),
'openid.response_nonce' => _openid_nonce(),
'openid.assoc_handle' => 'openid-test',
'openid.foo' => 123,
'openid.signed' => implode(',', $keys_to_sign),
);
$response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign);
variable_set('openid_test_response', $response);
$this->submitLoginForm($identity);
$this->assertNoRaw('OpenID login failed.');
$this->assertFieldByName('name', '', 'No username was supplied by provider.');
$this->assertFieldByName('mail', '', 'No e-mail address was supplied by provider.');
// Check that unsigned SREG fields are ignored.
$response = array(
'openid.signed' => 'op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle,sreg.nickname',
'openid.sreg.nickname' => 'john',
'openid.sreg.email' => 'john@example.com',
);
variable_set('openid_test_response', $response);
$this->submitLoginForm($identity);
$this->assertNoRaw('OpenID login failed.');
$this->assertFieldByName('name', 'john', 'Username was supplied by provider.');
$this->assertFieldByName('mail', '', 'E-mail address supplied by provider was ignored.');
}
}
/**
* Test account registration using Simple Registration and Attribute Exchange.
*/
class OpenIDRegistrationTestCase extends OpenIDWebTestCase {
public static function getInfo() {
return array(
'name' => 'OpenID account registration',
'description' => 'Creates a user account using auto-registration.',
'group' => 'OpenID'
);
}
function setUp() {
parent::setUp('openid', 'openid_test');
variable_set('user_register', USER_REGISTER_VISITORS);
}
/**
* Test OpenID auto-registration with e-mail verification enabled.
*/
function testRegisterUserWithEmailVerification() {
variable_set('user_email_verification', TRUE);
// Tell openid_test.module to respond with these SREG fields.
variable_set('openid_test_response', array('openid.sreg.nickname' => 'john', 'openid.sreg.email' => 'john@example.com'));
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->submitLoginForm($identity);
$this->assertRaw(t('Once you have verified your e-mail address, you may log in via OpenID.'), 'User was asked to verify e-mail address.');
$this->assertRaw(t('A welcome message with further instructions has been sent to your e-mail address.'), 'A welcome message was sent to the user.');
$reset_url = $this->getPasswordResetURLFromMail();
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertEqual($user->mail, 'john@example.com', 'User was registered with right email address.');
$this->assertFalse($user->data, 'No additional user info was saved.');
$this->submitLoginForm($identity);
$this->assertRaw(t('You must validate your email address for this account before logging in via OpenID.'));
// Follow the one-time login that was sent in the welcome e-mail.
$this->drupalGet($reset_url);
$this->drupalPost(NULL, array(), t('Log in'));
$this->drupalLogout();
// Verify that the account was activated.
$this->submitLoginForm($identity);
$this->assertLink(t('Log out'), 0, 'User was logged in.');
}
/**
* Test OpenID auto-registration with e-mail verification disabled.
*/
function testRegisterUserWithoutEmailVerification() {
variable_set('user_email_verification', FALSE);
// Tell openid_test.module to respond with these SREG fields.
variable_set('openid_test_response', array('openid.sreg.nickname' => 'john', 'openid.sreg.email' => 'john@example.com'));
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->submitLoginForm($identity);
$this->assertLink(t('Log out'), 0, 'User was logged in.');
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertEqual($user->mail, 'john@example.com', 'User was registered with right email address.');
$this->assertFalse($user->data, 'No additional user info was saved.');
$this->drupalLogout();
$this->submitLoginForm($identity);
$this->assertLink(t('Log out'), 0, 'User was logged in.');
}
/**
* Test OpenID auto-registration with a provider that supplies invalid SREG
* information (a username that is already taken, and no e-mail address).
*/
function testRegisterUserWithInvalidSreg() {
// Tell openid_test.module to respond with these SREG fields.
$web_user = $this->drupalCreateUser(array());
variable_set('openid_test_response', array('openid.sreg.nickname' => $web_user->name, 'openid.sreg.email' => 'mail@invalid#'));
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->submitLoginForm($identity);
$this->assertRaw(t('Account registration using the information provided by your OpenID provider failed due to the reasons listed below. Complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your OpenID under "My account".', array('@login' => url('user/login'))), 'User was asked to complete the registration process manually.');
$this->assertRaw(t('The name %name is already taken.', array('%name' => $web_user->name)), 'Form validation error for username was displayed.');
$this->assertRaw(t('The e-mail address %mail is not valid.', array('%mail' => 'mail@invalid#')), 'Form validation error for e-mail address was displayed.');
// Enter username and e-mail address manually.
$edit = array('name' => 'john', 'mail' => 'john@example.com');
$this->drupalPost(NULL, $edit, t('Create new account'));
$this->assertRaw(t('Once you have verified your e-mail address, you may log in via OpenID.'), 'User was asked to verify e-mail address.');
$reset_url = $this->getPasswordResetURLFromMail();
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertFalse($user->data, 'No additional user info was saved.');
// Follow the one-time login that was sent in the welcome e-mail.
$this->drupalGet($reset_url);
$this->drupalPost(NULL, array(), t('Log in'));
// The user is taken to user/%uid/edit.
$this->assertFieldByName('mail', 'john@example.com', 'User was registered with right e-mail address.');
$this->clickLink(t('OpenID identities'));
$this->assertRaw($identity, 'OpenID identity was registered.');
}
/**
* Test OpenID auto-registration with a provider that does not supply SREG
* information (i.e. no username or e-mail address).
*/
function testRegisterUserWithoutSreg() {
// Load the front page to get the user login block.
$this->drupalGet('');
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->submitLoginForm($identity);
$this->assertRaw(t('Complete the registration by filling out the form below. If you already have an account, you can <a href="@login">log in</a> now and add your OpenID under "My account".', array('@login' => url('user/login'))), 'User was asked to complete the registration process manually.');
$this->assertNoRaw(t('You must enter a username.'), 'Form validation error for username was not displayed.');
$this->assertNoRaw(t('You must enter an e-mail address.'), 'Form validation error for e-mail address was not displayed.');
// Enter username and e-mail address manually.
$edit = array('name' => 'john', 'mail' => 'john@example.com');
$this->drupalPost(NULL, $edit, t('Create new account'));
$this->assertRaw(t('Once you have verified your e-mail address, you may log in via OpenID.'), 'User was asked to verify e-mail address.');
$reset_url = $this->getPasswordResetURLFromMail();
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertFalse($user->data, 'No additional user info was saved.');
// Follow the one-time login that was sent in the welcome e-mail.
$this->drupalGet($reset_url);
$this->drupalPost(NULL, array(), t('Log in'));
// The user is taken to user/%uid/edit.
$this->assertFieldByName('mail', 'john@example.com', 'User was registered with right e-mail address.');
$this->clickLink(t('OpenID identities'));
$this->assertRaw($identity, 'OpenID identity was registered.');
}
/**
* Test OpenID auto-registration with a provider that supplies AX information,
* but no SREG.
*/
function testRegisterUserWithAXButNoSREG() {
variable_set('user_email_verification', FALSE);
// Tell openid_test.module to respond with these AX fields.
variable_set('openid_test_response', array(
'openid.ns.ext123' => 'http://openid.net/srv/ax/1.0',
'openid.ext123.type.mail456' => 'http://axschema.org/contact/email',
'openid.ext123.value.mail456' => 'john@example.com',
'openid.ext123.type.name789' => 'http://schema.openid.net/namePerson/friendly',
'openid.ext123.count.name789' => '1',
'openid.ext123.value.name789.1' => 'john',
));
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE));
$this->submitLoginForm($identity);
$this->assertLink(t('Log out'), 0, 'User was logged in.');
$user = user_load_by_name('john');
$this->assertTrue($user, 'User was registered with right username.');
$this->assertEqual($user->mail, 'john@example.com', 'User was registered with right email address.');
}
}
/**
* Test account registration using Simple Registration and Attribute Exchange.
*/
class OpenIDInvalidIdentifierTransitionTestCase extends OpenIDFunctionalTestCase {
public static function getInfo() {
return array(
'name' => 'OpenID account update',
'description' => 'Tries to correct OpenID identifiers attached to accounts if their identifiers were stripped.',
'group' => 'OpenID',
);
}
function setUp() {
parent::setUp('openid', 'openid_test');
variable_set('user_register', USER_REGISTER_VISITORS);
variable_set('openid_less_obtrusive_transition', TRUE);
}
/**
* Test OpenID transition with e-mail mismatch.
*/
function testStrippedFragmentAccountEmailMismatch() {
$this->drupalLogin($this->web_user);
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE, 'fragment' => $this->randomName()));
$identity_stripped = preg_replace('/#.*/', '', $identity);
// Add invalid identifier to the authmap (identifier has stripped fragment).
$this->addIdentity($identity_stripped);
$this->drupalLogout();
// Test logging in via the login form, provider will respond with full
// identifier (including fragment) but with different email, so we can't
// provide auto-update.
variable_set('openid_test_response', array(
'openid.claimed_id' => $identity,
'openid.sreg.nickname' => $this->web_user->name,
'openid.sreg.email' => 'invalid-' . $this->web_user->mail));
$edit = array('openid_identifier' => $identity_stripped);
$this->submitLoginForm($identity_stripped);
// Verify user was redirected away from user login to an accessible page.
$this->assertResponse(200);
// Verify the message.
$this->assertRaw(t('There is already an existing account associated with the OpenID identifier that you have provided.'), 'Message that OpenID identifier must be updated manually was displayed.');
}
/**
* Test OpenID auto transition with e-mail.
*/
function testStrippedFragmentAccountAutoUpdateSreg() {
$this->drupalLogin($this->web_user);
// Use a User-supplied Identity that is the URL of an XRDS document.
$identity = url('openid-test/yadis/xrds', array('absolute' => TRUE, 'fragment' => $this->randomName()));
$identity_stripped = preg_replace('/#.*/', '', $identity);
// Add invalid identifier to the authmap (identifier has stripped fragment).
$this->addIdentity($identity_stripped);
$this->drupalLogout();
// Test logging in via the login form, provider will respond with full
// identifier (including fragment) but with different email, so we can't
// provide auto-update.
variable_set('openid_test_response', array(
'openid.claimed_id' => $identity,
'openid.sreg.nickname' => $this->web_user->name,
'openid.sreg.email' => $this->web_user->mail));
$this->submitLoginForm($identity_stripped);
// Verify user was redirected away from user login to an accessible page.
$this->assertResponse(200);
// Verify the message.
$this->assertRaw(t('New OpenID identifier %identity was added as a replacement for invalid identifier %invalid_identity.', array('%invalid_identity' => $identity_stripped, '%identity' => $identity)), 'Message that OpenID identifier was added automatically was displayed.');
}
}
/**
* Test internal helper functions.
*/
class OpenIDTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'OpenID helper functions',
'description' => 'Test OpenID helper functions.',
'group' => 'OpenID'
);
}
function setUp() {
parent::setUp('openid');
module_load_include('inc', 'openid');
}
/**
* Test _openid_dh_XXX_to_XXX() functions.
*/
function testConversion() {
$this->assertIdentical(_openid_dh_long_to_base64('12345678901234567890123456789012345678901234567890'), 'CHJ/Y2mq+DyhUCZ0evjH8ZbOPwrS', '_openid_dh_long_to_base64() returned expected result.');
$this->assertIdentical(_openid_dh_base64_to_long('BsH/g8Nrpn2dtBSdu/sr1y8hxwyx'), '9876543210987654321098765432109876543210987654321', '_openid_dh_base64_to_long() returned expected result.');
$this->assertIdentical(_openid_dh_long_to_binary('12345678901234567890123456789012345678901234567890'), "\x08r\x7fci\xaa\xf8<\xa1P&tz\xf8\xc7\xf1\x96\xce?\x0a\xd2", '_openid_dh_long_to_binary() returned expected result.');
$this->assertIdentical(_openid_dh_binary_to_long("\x06\xc1\xff\x83\xc3k\xa6}\x9d\xb4\x14\x9d\xbb\xfb+\xd7/!\xc7\x0c\xb1"), '9876543210987654321098765432109876543210987654321', '_openid_dh_binary_to_long() returned expected result.');
}
/**
* Test _openid_dh_xorsecret().
*/
function testOpenidDhXorsecret() {
$this->assertEqual(_openid_dh_xorsecret('123456790123456790123456790', "abc123ABC\x00\xFF"), "\xa4'\x06\xbe\xf1.\x00y\xff\xc2\xc1", '_openid_dh_xorsecret() returned expected result.');
}
/**
* Test _openid_signature().
*/
function testOpenidSignature() {
// Test that signature is calculated according to OpenID Authentication 2.0,
// section 6.1. In the following array, only the two first entries should be
// included in the calculation, because the substring following the period
// is mentioned in the third argument for _openid_signature(). The last
// entry should not be included, because it does not start with "openid.".
$response = array(
'openid.foo' => 'abc1',
'openid.bar' => 'abc2',
'openid.baz' => 'abc3',
'foobar.foo' => 'abc4',
);
$association = new stdClass();
$association->mac_key = "1234567890abcdefghij\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9";
$this->assertEqual(_openid_signature($association, $response, array('foo', 'bar')), 'QnKZQzSFstT+GNiJDFOptdcZjrc=', 'Expected signature calculated.');
}
/**
* Test _openid_is_xri().
*/
function testOpenidXRITest() {
// Test that the XRI test is according to OpenID Authentication 2.0,
// section 7.2. If the user-supplied string starts with xri:// it should be
// stripped and the resulting string should be treated as an XRI when it
// starts with "=", "@", "+", "$", "!" or "(".
$this->assertTrue(_openid_is_xri('xri://=foo'), '_openid_is_xri() returned expected result for an xri identifier with xri scheme.');
$this->assertTrue(_openid_is_xri('xri://@foo'), '_openid_is_xri() returned expected result for an xri identifier with xri scheme.');
$this->assertTrue(_openid_is_xri('xri://+foo'), '_openid_is_xri() returned expected result for an xri identifier with xri scheme.');
$this->assertTrue(_openid_is_xri('xri://$foo'), '_openid_is_xri() returned expected result for an xri identifier with xri scheme.');
$this->assertTrue(_openid_is_xri('xri://!foo'), '_openid_is_xri() returned expected result for an xri identifier with xri scheme..');
$this->assertTrue(_openid_is_xri('xri://(foo'), '_openid_is_xri() returned expected result for an xri identifier with xri scheme..');
$this->assertTrue(_openid_is_xri('=foo'), '_openid_is_xri() returned expected result for an xri identifier.');
$this->assertTrue(_openid_is_xri('@foo'), '_openid_is_xri() returned expected result for an xri identifier.');
$this->assertTrue(_openid_is_xri('+foo'), '_openid_is_xri() returned expected result for an xri identifier.');
$this->assertTrue(_openid_is_xri('$foo'), '_openid_is_xri() returned expected result for an xri identifier.');
$this->assertTrue(_openid_is_xri('!foo'), '_openid_is_xri() returned expected result for an xri identifier.');
$this->assertTrue(_openid_is_xri('(foo'), '_openid_is_xri() returned expected result for an xri identifier.');
$this->assertFalse(_openid_is_xri('foo'), '_openid_is_xri() returned expected result for an http URL.');
$this->assertFalse(_openid_is_xri('xri://foo'), '_openid_is_xri() returned expected result for an http URL.');
$this->assertFalse(_openid_is_xri('http://foo/'), '_openid_is_xri() returned expected result for an http URL.');
$this->assertFalse(_openid_is_xri('http://example.com/'), '_openid_is_xri() returned expected result for an http URL.');
$this->assertFalse(_openid_is_xri('user@example.com/'), '_openid_is_xri() returned expected result for an http URL.');
$this->assertFalse(_openid_is_xri('http://user@example.com/'), '_openid_is_xri() returned expected result for an http URL.');
}
/**
* 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', 'openid_normalize() correctly normalized an XRI.');
$this->assertEqual(openid_normalize('xri://$foo'), '$foo', 'openid_normalize() correctly normalized an XRI with an xri:// scheme.');
$this->assertEqual(openid_normalize('example.com/'), 'http://example.com/', 'openid_normalize() correctly normalized a URL with a missing scheme.');
$this->assertEqual(openid_normalize('example.com'), 'http://example.com/', 'openid_normalize() correctly normalized a URL with a missing scheme and empty path.');
$this->assertEqual(openid_normalize('http://example.com'), 'http://example.com/', 'openid_normalize() correctly normalized a URL with an empty path.');
$this->assertEqual(openid_normalize('http://example.com/path'), 'http://example.com/path', 'openid_normalize() correctly normalized a URL with a path.');
$this->assertEqual(openid_normalize('http://example.com/path#fragment'), 'http://example.com/path', 'openid_normalize() correctly normalized a URL with a fragment.');
}
/**
* Test openid_extract_namespace().
*/
function testOpenidExtractNamespace() {
$response = array(
'openid.sreg.nickname' => 'john',
'openid.ns.ext1' => OPENID_NS_SREG,
'openid.ext1.nickname' => 'george',
'openid.ext1.email' => 'george@example.com',
'openid.ns.ext2' => 'http://example.com/ns/ext2',
'openid.ext2.foo' => '123',
'openid.ext2.bar' => '456',
'openid.signed' => 'sreg.nickname,ns.ext1,ext1.email,ext2.foo',
);
$values = openid_extract_namespace($response, 'http://example.com/ns/dummy', NULL, FALSE);
$this->assertEqual($values, array(), 'Nothing found for unused namespace.');
$values = openid_extract_namespace($response, 'http://example.com/ns/dummy', 'sreg', FALSE);
$this->assertEqual($values, array('nickname' => 'john'), 'Value found for fallback prefix.');
$values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', FALSE);
$this->assertEqual($values, array('nickname' => 'george', 'email' => 'george@example.com'), 'Namespace takes precedence over fallback prefix.');
// ext1.email is signed, but ext1.nickname is not.
$values = openid_extract_namespace($response, OPENID_NS_SREG, 'sreg', TRUE);
$this->assertEqual($values, array('email' => 'george@example.com'), 'Unsigned namespaced fields ignored.');
$values = openid_extract_namespace($response, 'http://example.com/ns/ext2', 'sreg', FALSE);
$this->assertEqual($values, array('foo' => '123', 'bar' => '456'), 'Unsigned fields found.');
// ext2.foo and ext2.bar are ignored, because ns.ext2 is not signed. The
// fallback prefix is not used, because the namespace is specified.
$values = openid_extract_namespace($response, 'http://example.com/ns/ext2', 'sreg', TRUE);
$this->assertEqual($values, array(), 'Unsigned fields ignored.');
}
}

View file

@ -0,0 +1,13 @@
name = OpenID dummy provider
description = "OpenID provider used for testing."
package = Testing
version = VERSION
core = 7.x
dependencies[] = openid
hidden = TRUE
; Information added by Drupal.org packaging script on 2017-06-21
version = "7.56"
project = "drupal"
datestamp = "1498069849"

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* Install, update and uninstall functions for the openid_test module.
*/
/**
* Implements hook_install().
*/
function openid_test_install() {
module_load_include('inc', 'openid');
// Generate a MAC key (Message Authentication Code) used for signing messages.
// The variable is base64-encoded, because variables cannot contain non-UTF-8
// data.
variable_set('openid_test_mac_key', drupal_random_key(20));
}

View file

@ -0,0 +1,368 @@
<?php
/**
* @file
* Dummy OpenID Provider used with SimpleTest.
*
* The provider simply responds positively to all authentication requests. In
* addition to a Provider Endpoint (a URL used for Drupal to communicate with
* the provider using the OpenID Authentication protocol) the module provides
* URLs used by the various discovery mechanisms.
*
* When a user enters an OpenID identity, the Relying Party (in the testing
* scenario, this is the OpenID module) looks up the URL of the Provider
* Endpoint using one of several discovery mechanisms. The Relying Party then
* redirects the user to Provider Endpoint. The provider verifies the user's
* identity and redirects the user back to the Relying Party accompanied by a
* signed message confirming the identity. Before redirecting to a provider for
* the first time, the Relying Party fetches a secret MAC key from the provider
* by doing a direct "associate" HTTP request to the Provider Endpoint. This
* key is used for verifying the signed messages from the provider.
*/
/**
* Implements hook_menu().
*/
function openid_test_menu() {
$items['openid-test/yadis/xrds'] = array(
'title' => 'XRDS service document',
'page callback' => 'openid_test_yadis_xrds',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/yadis/x-xrds-location'] = array(
'title' => 'Yadis discovery using X-XRDS-Location header',
'page callback' => 'openid_test_yadis_x_xrds_location',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/yadis/http-equiv'] = array(
'title' => 'Yadis discovery using <meta http-equiv="X-XRDS-Location" ...>',
'page callback' => 'openid_test_yadis_http_equiv',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/html/openid1'] = array(
'title' => 'HTML-based discovery using <link rel="openid.server" ...>',
'page callback' => 'openid_test_html_openid1',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/html/openid2'] = array(
'title' => 'HTML-based discovery using <link rel="openid2.provider" ...>',
'page callback' => 'openid_test_html_openid2',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/endpoint'] = array(
'title' => 'OpenID Provider Endpoint',
'page callback' => 'openid_test_endpoint',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/redirect'] = array(
'title' => 'OpenID Provider Redirection Point',
'page callback' => 'openid_test_redirect',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['openid-test/redirected/%/%'] = array(
'title' => 'OpenID Provider Final URL',
'page callback' => 'openid_test_redirected_method',
'page arguments' => array(2, 3),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_menu_site_status_alter().
*/
function openid_test_menu_site_status_alter(&$menu_site_status, $path) {
// Allow access to openid endpoint and identity even in offline mode.
if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && in_array($path, array('openid-test/yadis/xrds', 'openid-test/endpoint'))) {
$menu_site_status = MENU_SITE_ONLINE;
}
}
/**
* 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 '<?xml version="1.0" encoding="UTF-8"?>';
if (!empty($_GET['doctype'])) {
print "\n<!DOCTYPE dct [ <!ELEMENT blue (#PCDATA)> ]>\n";
}
print '
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)" xmlns:openid="http://openid.net/xmlns/1.0">
<XRD>
<Status cid="' . check_plain(variable_get('openid_test_canonical_id_status', 'verified')) . '"/>
<ProviderID>xri://@</ProviderID>
<CanonicalID>http://example.com/user</CanonicalID>
<Service>
<Type>http://example.com/this-is-ignored</Type>
</Service>
<Service priority="5">
<Type>http://openid.net/signon/1.0</Type>
<URI>http://example.com/this-is-only-openid-1.0</URI>
</Service>
<Service priority="10">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
<LocalID>http://example.com/xrds</LocalID>
</Service>
<Service priority="15">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<URI>http://example.com/this-has-too-low-priority</URI>
</Service>
<Service>
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<URI>http://example.com/this-has-too-low-priority</URI>
</Service>
';
if (arg(3) == 'server') {
print '
<Service>
<Type>http://specs.openid.net/auth/2.0/server</Type>
<URI>http://example.com/this-has-too-low-priority</URI>
</Service>
<Service priority="20">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
<LocalID>' . url('openid-test/yadis/xrds/server', array('absolute' => TRUE)) . '</LocalID>
</Service>';
}
elseif (arg(3) == 'delegate') {
print '
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/signon</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
<openid:Delegate>http://example.com/xrds-delegate</openid:Delegate>
</Service>';
}
print '
</XRD>
</xrds:XRDS>';
}
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.
*/
function openid_test_yadis_x_xrds_location() {
drupal_add_http_header('X-XRDS-Location', url('openid-test/yadis/xrds', array('absolute' => TRUE)));
return t('This page includes an X-RDS-Location HTTP header containing the URL of an XRDS document.');
}
/**
* Menu callback; regular HTML page with <meta> element.
*/
function openid_test_yadis_http_equiv() {
$element = array(
'#tag' => 'meta',
'#attributes' => array(
'http-equiv' => 'X-XRDS-Location',
'content' => url('openid-test/yadis/xrds', array('absolute' => TRUE)),
),
);
drupal_add_html_head($element, 'openid_test_yadis_http_equiv');
return t('This page includes a &lt;meta equiv=...&gt; element containing the URL of an XRDS document.');
}
/**
* Menu callback; regular HTML page with OpenID 1.0 <link> element.
*/
function openid_test_html_openid1() {
drupal_add_html_head_link(array('rel' => 'openid.server', 'href' => url('openid-test/endpoint', array('absolute' => TRUE))));
drupal_add_html_head_link(array('rel' => 'openid.delegate', 'href' => 'http://example.com/html-openid1'));
return t('This page includes a &lt;link rel=...&gt; element containing the URL of an OpenID Provider Endpoint.');
}
/**
* Menu callback; regular HTML page with OpenID 2.0 <link> element.
*/
function openid_test_html_openid2() {
drupal_add_html_head_link(array('rel' => 'openid2.provider', 'href' => url('openid-test/endpoint', array('absolute' => TRUE))));
drupal_add_html_head_link(array('rel' => 'openid2.local_id', 'href' => 'http://example.com/html-openid2'));
return t('This page includes a &lt;link rel=...&gt; element containing the URL of an OpenID Provider Endpoint.');
}
/**
* Menu callback; OpenID Provider Endpoint.
*
* It accepts "associate" requests directly from the Relying Party, and
* "checkid_setup" requests made by the user's browser based on HTTP redirects
* (in OpenID 1) or HTML forms (in OpenID 2) generated by the Relying Party.
*/
function openid_test_endpoint() {
switch ($_REQUEST['openid_mode']) {
case 'associate':
_openid_test_endpoint_associate();
break;
case 'checkid_setup':
_openid_test_endpoint_authenticate();
break;
}
}
/**
* Menu callback; redirect during Normalization/Discovery.
*/
function openid_test_redirect($count = 0) {
if ($count == 0) {
$url = variable_get('openid_test_redirect_url', '');
}
else {
$url = url('openid-test/redirect/' . --$count, array('absolute' => TRUE));
}
$http_response_code = variable_get('openid_test_redirect_http_reponse_code', 301);
header('Location: ' . $url, TRUE, $http_response_code);
exit();
}
/**
* Menu callback; respond with appropriate callback.
*/
function openid_test_redirected_method($method1, $method2) {
return call_user_func('openid_test_' . $method1 . '_' . $method2);
}
/**
* OpenID endpoint; handle "associate" requests (see OpenID Authentication 2.0,
* section 8).
*
* The purpose of association is to send the secret MAC key to the Relying Party
* using Diffie-Hellman key exchange. The MAC key is used in subsequent
* "authenticate" requests. The "associate" request is made by the Relying Party
* (in the testing scenario, this is the OpenID module that communicates with
* the endpoint using drupal_http_request()).
*/
function _openid_test_endpoint_associate() {
module_load_include('inc', 'openid');
// Use default parameters for Diffie-Helmann key exchange.
$mod = OPENID_DH_DEFAULT_MOD;
$gen = OPENID_DH_DEFAULT_GEN;
// Generate private Diffie-Helmann key.
$r = _openid_dh_rand($mod);
$private = _openid_math_add($r, 1);
// Calculate public Diffie-Helmann key.
$public = _openid_math_powmod($gen, $private, $mod);
// Calculate shared secret based on Relying Party's public key.
$cpub = _openid_dh_base64_to_long($_REQUEST['openid_dh_consumer_public']);
$shared = _openid_math_powmod($cpub, $private, $mod);
// Encrypt the MAC key using the shared secret.
$enc_mac_key = base64_encode(_openid_dh_xorsecret($shared, base64_decode(variable_get('mac_key'))));
// Generate response including our public key and the MAC key. Using our
// public key and its own private key, the Relying Party can calculate the
// shared secret, and with this it can decrypt the encrypted MAC key.
$response = array(
'ns' => 'http://specs.openid.net/auth/2.0',
'assoc_handle' => 'openid-test',
'session_type' => $_REQUEST['openid_session_type'],
'assoc_type' => $_REQUEST['openid_assoc_type'],
'expires_in' => '3600',
'dh_server_public' => _openid_dh_long_to_base64($public),
'enc_mac_key' => $enc_mac_key,
);
// Respond to Relying Party in the special Key-Value Form Encoding (see OpenID
// Authentication 1.0, section 4.1.1).
drupal_add_http_header('Content-Type', 'text/plain');
print _openid_create_message($response);
}
/**
* OpenID endpoint; handle "authenticate" requests.
*
* All requests result in a successful response. The request is a GET or POST
* made by the user's browser based on an HTML form or HTTP redirect generated
* by the Relying Party. The user is redirected back to the Relying Party using
* a URL containing a signed message in the query string confirming the user's
* identity.
*/
function _openid_test_endpoint_authenticate() {
module_load_include('inc', 'openid');
$expected_identity = variable_get('openid_test_identity');
if ($expected_identity && $_REQUEST['openid_identity'] != $expected_identity) {
$response = variable_get('openid_test_response', array()) + array(
'openid.ns' => OPENID_NS_2_0,
'openid.mode' => 'error',
'openid.error' => 'Unexpted identity',
);
drupal_add_http_header('Content-Type', 'text/plain');
header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
return;
}
// Generate unique identifier for this authentication.
$nonce = _openid_nonce();
// Generate response containing the user's identity.
$response = variable_get('openid_test_response', array()) + array(
'openid.ns' => OPENID_NS_2_0,
'openid.mode' => 'id_res',
'openid.op_endpoint' => url('openid-test/endpoint', array('absolute' => TRUE)),
'openid.claimed_id' => !empty($_REQUEST['openid_claimed_id']) ? $_REQUEST['openid_claimed_id'] : '',
'openid.identity' => $_REQUEST['openid_identity'],
'openid.return_to' => $_REQUEST['openid_return_to'],
'openid.response_nonce' => $nonce,
'openid.assoc_handle' => 'openid-test',
);
if (isset($response['openid.signed'])) {
$keys_to_sign = explode(',', $response['openid.signed']);
}
else {
// Unless openid.signed is explicitly defined, all keys are signed.
$keys_to_sign = array();
foreach ($response as $key => $value) {
// Strip off the "openid." prefix.
$keys_to_sign[] = substr($key, 7);
}
$response['openid.signed'] = implode(',', $keys_to_sign);
}
// Sign the message using the MAC key that was exchanged during association.
$association = new stdClass();
$association->mac_key = variable_get('mac_key');
if (!isset($response['openid.sig'])) {
$response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign);
}
// Put the signed message into the query string of a URL supplied by the
// Relying Party, and redirect the user.
drupal_add_http_header('Content-Type', 'text/plain');
header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
}